比较有难度的swift自定义转场动画
SamShackelf
8年前
<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>