比较有难度的swift自定义转场动画

SamShackelf 4年前
   <h2>一 转场效果图和采用转场方式</h2>    <p>1 转场效果图 :</p>    <p><img alt="这里写图片描述" src="https://simg.open-open.com/show/568cd7d717e926e7cd66408ae859e619.gif"></p>    <p>2 采用方式 (方法):</p>    <p>—-> 2.1 自定义转场动画</p>    <p>—-> 2.2 协议</p>    <h2>二 转场实现需要获取的东西</h2>    <p>1 获取转场前图片的frame</p>    <p>2 设置一张临时imageView作为转场图片(图片并不是真实存在的)</p>    <p>3 获取图片放大展示的frame</p>    <h2>三 转场图解</h2>    <p><img alt="这里写图片描述" src="https://simg.open-open.com/show/f45bad8a077c5097eb949d399652be0c.png"></p>    <h2>四 转场动画思想</h2>    <p>1 通过在实现转场动画的类中定义协议方法,定义代理属性,明确谁可以提供需要的frame和imageView,将对方设置为代理,让代理提供需求,达到转场目的.</p>    <p>2 明确代码的书写位置,在哪里可以实现转场动画.</p>    <h2>五 协议(弹出动画)</h2>    <p>1 协议书写位置</p>    <p><img alt="这里写图片描述" src="https://simg.open-open.com/show/c977293eff907dcca1cf261a895f77ab.png"></p>    <p>2 协议方法 : (可以让外界提供需要的东西)</p>    <pre>   //MARK: - 定义协议用来拿到图片起始位置;最终位置和图片  protocol PersentDelegate : class {      //起始位置      func homeRect(indexPath : NSIndexPath) ->CGRect      //展示图片的位置      func photoBrowserRect(indexPath : NSIndexPath) ->CGRect      //获取imageView(用这张临时的图片来实现动画效果)      func imageView(indexPath : NSIndexPath) ->UIImageView  }</pre>    <p>3 在该该类中设置代理</p>    <pre>  //设置代理(代理一般都是用weak修饰)      weak var presentDelegate : PersentDelegate?</pre>    <p>4 实现转场动画的代码书写位置 : (上一篇送已经说明转场动画的书写位置)</p>    <p>—-> 4.1 得到一张图片</p>    <p>—-> 4.2 得到home中图片的起始frame</p>    <p>—-> 4.3 展示图片的frame</p>    <p>—-> 4.4 完成动画</p>    <p>—-> 4.5 图片由透明到清晰,通过alpha来实现</p>    <pre>  //获取转场的上下文:可以通过上下文获取到执行动画的view      func animateTransition(transitionContext: UIViewControllerContextTransitioning) {          isPresented ? animateForPresentView(transitionContext) : animateForDismissed(transitionContext)      }      //弹出      func animateForPresentView(transitionContext: UIViewControllerContextTransitioning) {          //1 取出弹出的view          let presentView = transitionContext.viewForKey(UITransitionContextToViewKey)!          //2 将弹出的view加入到contentView中          transitionContext.containerView()?.addSubview(presentView)          //3 执行动画          //3.1 获取需要执行的imageView          guard let persentDelegate = presentDelegate else {              return          }          guard let indexPath = indexPath else {              return          }          //调用方法,得到一张图片          let imageView = persentDelegate.imageView(indexPath)          //将图片添加到父控件中          transitionContext.containerView()?.addSubview(imageView)          //设置imageView的起始位置          imageView.frame = persentDelegate.homeRect(indexPath)          presentView.alpha = 0          //设置弹出动画的时候背景颜色为黑色          transitionContext.containerView()?.backgroundColor = UIColor.blackColor()          UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in              //设置展示图片的位置              imageView.frame = persentDelegate.photoBrowserRect(indexPath)              }) { (_) -> Void in              //修改图片透明度              presentView.alpha = 1.0              //移除图片              imageView.removeFromSuperview()              transitionContext.containerView()?.backgroundColor = UIColor.clearColor()              //完成动画              transitionContext.completeTransition(true)          }      }</pre>    <h2>六 传值</h2>    <p>1 如何通过协议获取home中某张图片的frame值?</p>    <p>—-> 1.1 通过在扎UN擦汗那个动画实现的方法中设置一个indexPath(用于外界传入角标)来确定frame</p>    <pre>  class PhotoBrowserAnimator: NSObject {      //设置角标      var indexPath : NSIndexPath?  }</pre>    <p>2 设置代理对象</p>    <p><img alt="这里写图片描述" src="https://simg.open-open.com/show/8af24730c6c31152f4b36c99605c717c.png"></p>    <pre>  //Mark: - 点击图片,弹出控制器  extension HomeViewController {          //定义为私有的          private func showPhotoBrowser( indexPath : NSIndexPath) {          //将图片的角标传入          photoBrowserAnimator.indexPath = indexPath          //设置代理          photoBrowserAnimator.presentDelegate = self  }</pre>    <p>3 实现协议方法(该部分代码需要写的比较多,所以扩充一个方法)</p>    <p>—-> 3.1 获取home中图片具体的frame值(重点掌握 : 如何将cell的frame值转为cell在window中的坐标值)</p>    <pre>  //MARK: - 实现协议中的方法  extension HomeViewController : PersentDelegate {      func homeRect(indexPath: NSIndexPath) -> CGRect {          //取出cell           let cell = (collectionView?.cellForItemAtIndexPath(indexPath))!          //将cell的frame值转成cell在window中的坐标          let homeFrame = collectionView!.convertRect(cell.frame, toCoordinateSpace: UIApplication.sharedApplication().keyWindow!)          //返回具体的尺寸          return homeFrame      }  }</pre>    <p>—-> 3.2 返回一张作为转场动画的临时图片(在同一个方法中书写)</p>    <pre>  func imageView(indexPath: NSIndexPath) -> UIImageView {          //创建imageView对象          let imageView = UIImageView()          //取出cell          let cell = (collectionView?.cellForItemAtIndexPath(indexPath))! as! HomeViewCell          //取出cell中显示的图片          let image = cell.imageView.image          //设置图片          imageView.image = image          //设置imageView相关属性(拉伸模式)          imageView.contentMode = .ScaleAspectFill          //将多余的部分裁剪          imageView.clipsToBounds = true          //返回图片          return imageView      }</pre>    <p>—-> 3.3 返回最终展示图片的frame值</p>    <pre>  func photoBrowserRect(indexPath: NSIndexPath) -> CGRect {          //取出cell          let cell = (collectionView?.cellForItemAtIndexPath(indexPath))! as! HomeViewCell          //取出cell中显示的图片          let image = cell.imageView.image          //计算方法后的图片的frame          return calculate(image!)      }</pre>    <p>—-> 3.4 如何计算根据传入的图片值,得出最终展示的图片值? 解答如下</p>    <p>4 创建一个类,内部只是提供一个计算展示图片的frame(外界也是可以调用的)</p>    <p><img alt="这里写图片描述" src="https://simg.open-open.com/show/391ccebf4ef87ca27bef134a8f6a21c6.png"></p>    <p>—-> 4.1 类中代码 :</p>    <pre>  import UIKit    //MARK: - 设置图片的frame      func calculate (image : UIImage) ->CGRect {      let imageViewX : CGFloat = 0.0      let imageViewW : CGFloat = UIScreen.mainScreen().bounds.width      let imageViewH : CGFloat = imageViewW / image.size.width * image.size.height      let imageViewY : CGFloat = (UIScreen.mainScreen().bounds.height - imageViewH) * 0.5        return CGRect(x: imageViewX, y: imageViewY, width: imageViewW, height: imageViewH)  }</pre>    <h2>七 协议(消失动画)</h2>    <p>1 和弹出动画一样的道理,但是该部分只需要获取当前展示图片的indexPath和imageView</p>    <p>2 明确谁可以提供这部分需求;将代理设置给谁?代码书写的位置?</p>    <p>3 定义协议 : (在转场动画的类中实现协议方法定义)</p>    <pre>  //MARK: - 定义协议来实现消失动画  protocol DismissDelegate : class {      //获取当前显示的图片的角标      func currentIndexPath() ->NSIndexPath      //获取imageView(用这张临时的图片来实现动画效果)      func imageView() ->UIImageView  }</pre>    <p>4 设置代理</p>    <pre>  class PhotoBrowserAnimator: NSObject {      //设置消失动画的代理      weak var dismissDelegate : DismissDelegate?  }</pre>    <p>5 消失动画</p>    <p>—-> 5.1 获取当前展示图片位置</p>    <p>—-> 5.2 获取imageView</p>    <p>—-> 5.3 根据用户滑动后的角标,来显示最终消失动画后图片在home的位置(用presentDelegate代理)</p>    <p>—-> 5.4 代码 :</p>    <pre>  //消失      func animateForDismissed (transitionContext: UIViewControllerContextTransitioning) {          //1 取出消失的view          let dismissView = transitionContext.viewForKey(UITransitionContextFromViewKey)!          dismissView.removeFromSuperview()          //2 执行动画          //2.1 校验(如果没有值,就直接返回)          guard let dismissDelegate = dismissDelegate else {              return          }          guard let presentDelegate = presentDelegate else {              return          }          // 2.2获取一张图片          let imageView = dismissDelegate.imageView()          // 2.3将图片添加到父控件中          transitionContext.containerView()?.addSubview(imageView)          // 2.4 获取当前正在显示的图片的角标          let indexPath = dismissDelegate.currentIndexPath()          // 2.5 设置起始位置          imageView.frame = presentDelegate.photoBrowserRect(indexPath)          //执行动画          UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in             imageView.frame = presentDelegate.homeRect(indexPath)              }) { (_) -> Void in              //完成动画              transitionContext.completeTransition(true)          }      }  }</pre>    <p>6 设置代理(因为创建PhotoBrowserController是在HomeViewController类中创建的)</p>    <pre>  //Mark: - 点击图片,弹出控制器  extension HomeViewController {          //定义为私有的          private func showPhotoBrowser( indexPath : NSIndexPath) {          //创建控制器对象          let showPhotoBrowserVC = PhotoBrowserController()          //设置dismiss的代理          photoBrowserAnimator.dismissDelegate = showPhotoBrowserVC  }</pre>    <p>—-> 6.1 明确只有展示图片的控制器才能提供这些需求,所以将其设置为代理</p>    <p>7 实现代理方法</p>    <p><img alt="这里写图片描述" src="https://simg.open-open.com/show/af46b35b59bde1fbece04a7c7a9e3050.png"></p>    <p>—-> 7.1 根据cell获取当前的indexPath值</p>    <pre>  //Mark: - 实现dismiss的代理方法  extension PhotoBrowserController : DismissDelegate {      func currentIndexPath() -> NSIndexPath {          //获取当前正在显示的cell          let cell = collectionView.visibleCells().first!          //根据cell获取indexPath          return collectionView.indexPathForCell(cell)!      }  }    </pre>    <p>—-> 7.2 返回一张图片(和7.1写在同一个方法中)</p>    <pre>  func imageView() -> UIImageView {          //创建UIImageView对象          let imageView = UIImageView()          //获取正在显示的cell          let cell = collectionView.visibleCells().first! as! PhotoBrowerViewCell          //取出cell中的图片          imageView.image = cell.imageView.image          //设置图片的属性          imageView.contentMode = .ScaleAspectFill          //将多出的图片裁剪          imageView.clipsToBounds = true          //返回图片          return imageView      }</pre>    <h2>八 细节处理</h2>    <p>1 在弹出动画中点击home中的图片通过转场动画成大图,那么会发现在图片变大的过程中home背景并没有消失,而是完全展示了大图才消失的.怎么解决?</p>    <p>—-> 1.1 通过在动画的时候将背景颜色设置为黑色,遮住home</p>    <pre>  //设置弹出动画的时候背景颜色为黑色          transitionContext.containerView()?.backgroundColor = UIColor.blackColor()</pre>    <p>—-> 1.2 等动画完成后再讲颜色设置为clearColor</p>    <pre>  transitionContext.containerView()?.backgroundColor = UIColor.clearColor()</pre>    <p>2 一定不要忘记转场动画完成后需要告诉系统转场动画完成,否则会出现莫名其妙的情况.</p>    <p>3 尽量的要对代理是否有值进行校验,但是也可以通过强制解包(不是很安全),还是通过判断安全点.</p>    <h2>九 总结</h2>    <p>1 该部分比较有难度,是对上一篇转场动画的加强.如果有看不懂的读者也是很正常,有什么问题可以直接给我留言,我会尽可能的解答.</p>    <p>2 代码可能考虑的还不是很完善,我只是尽量的去完成.最后如果大家觉得我写得还行,麻烦大家关注我的官方博客,谢谢!!!!</p>    <p>来自: <a href="/misc/goto?guid=4959672125209825665" rel="nofollow">http://blog.csdn.net/xf931456371/article/details/51288824</a></p>