iOS开发——圆形过渡动画
来源:互联网 发布:droid4x for mac 编辑:程序博客网 时间:2024/05/16 10:56
前言
在一款新的app——Ping中,用户可以订阅自己感兴趣的主题,该应用会向用户推送相关的文章或段落。该应用在视图的切换时采用了一个非常炫酷的动画效果,如下图所示:
现在我们就来实现这一效果。总的来说,所用到的知识点有:
1、使用代理UIViewControllerAnimatedTransitioning实现控制器间的自定义动画
2、使用UIShapeLayer创建一个特定形状的层
3、配合mask效果实现视图的切换
4、使用手势与UIPercentDrivenInteractiveTransition实现可控过程的交互。
实现思路
结构分析
首先,这里的切换效果是控制器间的过渡效果,并且是基于UINavigationContoller的,因为我们需要通过导航控制器的代理来实现。8.0以后,UINavigationController提供了一个代理UINavigationControllerDelegate可以让我们对控制器间的pop、push过程进行控制,代理中有一个方法:
func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
这个方法的返回值是一个实现了UIViewControllerAnimatedTransitioning协议的对象,这是我们项目中另外一个协议。用它可以控制动画时间、编写自定义动画流程、对动画完成后的资源进行回收处理等。
总的来说,我们需要的东西有:
1、两个控制器供切换
2、一个UINavigationControllerDelegate对象控制视图控制器的切换流程
3、一个UIViewControllerAnimatedTransitioning对象提供具体的动画
功能分析
当另一个控制器将要展示内容时,从上图可以看到,首先从右上角出现一个圆形,圆形不断放大,将要展示的内容就在圆形中,圆形之外依然是原来控制器的内容。动画过程中,这个圆形不断放大,随着它的增大,新内容也就慢慢出来了。换句话说,这个圆形的作用就是展示范围内的,屏蔽范围外的。
这里用遮挡层来实现是再好不过了,mask属性就决定着这一遮挡过程。当然了mask需要一个CALayer对象,这里也说过,这个过程中出现的是圆形,对于特定的形状,CAShapeLayer就能方便的解决问题了。
具体实现
有了思路,实现起来就简单了。首先要准备好两个控制器,基本的要求就是提供一个按钮可以用来视图切换即可:
这里从左到右依次是UINavigationController、ViewController、ViewController,右上角的按钮(圆角效果)用来切换,图片的位置乱是由于使用了auto layout,加上只有一张图片,懒得调了:]
第一步,动画的实现
既然动画的具体过程是由UIViewControllerAnimatedTransitioning对象来实现的,那么我们先创建这个对象。UIViewControllerAnimatedTransitioning是一个协议,该协议中有两个必须方法和一个可选方法:
func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeIntervalfunc animateTransition(transitionContext: UIViewControllerContextTransitioning)optional func animationEnded(transitionCompleted: Bool)
按照上面说的,我们单独建一个类,让新类实现该协议,并编写这三个方法:
class CircleTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { //context contains fromViewController、toViewController、containView etc. //We keep a reference here to get the context during the animation weak var transitionContext: UIViewControllerContextTransitioning? //duration time func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return 0.5 } //animation progress func animateTransition(transitionContext: UIViewControllerContextTransitioning) { //get the context self.transitionContext = transitionContext //get other important variable var containView = transitionContext.containerView() var fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as ViewController var toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as ViewController //button here means the button that controls the changing progress in fromViewController //we will let the circle start from button's center var button = fromVC.button containView.addSubview(toVC.view) //calculate some values let circlePathInitial = UIBezierPath(ovalInRect: button.frame) //just big enough is OK, too. Because here we just need to make sure the mask can cover all view let extremePoint = CGPoint(x: button.center.x, y: button.center.y - toVC.view.bounds.size.height) let radius = sqrt(extremePoint.x * extremePoint.x + extremePoint.y * extremePoint.y) let circlePathFinal = UIBezierPath(ovalInRect: CGRectInset(button.frame, -radius, -radius)) //create shape layer let shapeLayer = CAShapeLayer() shapeLayer.path = circlePathFinal.CGPath toVC.view.layer.mask = shapeLayer //create animation and add it let maskAnimation = CABasicAnimation(keyPath: "path") maskAnimation.fromValue = circlePathInitial.CGPath maskAnimation.toValue = circlePathFinal.CGPath maskAnimation.duration = self.transitionDuration(transitionContext) maskAnimation.delegate = self shapeLayer.addAnimation(maskAnimation, forKey: "path") } override func animationDidStop(anim: CAAnimation!, finished flag: Bool) { //end the animation self.transitionContext?.completeTransition(!self.transitionContext!.transitionWasCancelled()) //remove the mask at fromViewController self.transitionContext?.viewControllerForKey(UITransitionContextFromViewControllerKey)?.view.layer.mask = nil }}
注释中已经解释得非常详细了,这里从上下文中获取两个控制器,基于此创建CAShapeLayer,通过设置起始路径(主要是半径不同),并赋值给动画的起始属性,来完成这个流程。
第二步,动画的控制
接下来我们需要UINavigationControllerDelegate对象来控制上面完成的动画,依然新建一个类,实现该协议,并编写相关方法:
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate { func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { return CircleTransitionAnimator() }}
第三步。连接
最后,为我们跟控制器(导航控制器)的delegate赋值上面这个类的一个对象即可。这个过程可以在storyboard中完成,拖一个Object对象到UINavigationController中,将类改为NavigationControllerDelegate,然后把导航控制器的delegate拖到该对象上即可
现在已经可以看到效果了,并且跟Ping类似:
手势操作
这样我们的动画就完成了。接下来,我们来把按钮给“消灭”掉。
随着手势的流行,我们可以用手势来实现很多操作,并且这些过程往往是可控的,典型的例子就是前一阵很火的抽屉菜单。
现在我们为UINavigationController添加一个pan手势,让视图切换效果受用户拖动控制。
UINavigationControllerDelegate提供了一个方法用来返回“交互过程”:
func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
UIViewControllerInteractiveTransitioning是一个代理,UIPercentDrivenInteractiveTransition便是iOS为我们提供的一个实现了这一代理的类,该类可以按比例更新视图切换过程、直接完成切换、取消切换……因此,我们首先需要一个该类对象。另外,在UINavigationControllerDelegate中,我们也要获得UINavigationController的引用,因此声明两个变量:
var interactionController: UIPercentDrivenInteractiveTransition?@IBOutlet weak var navigationVC: UINavigationController?
添加Pan手势:
override func awakeFromNib() { super.awakeFromNib() let panGR: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: Selector("handlePan:")) self.navigationVC?.view.addGestureRecognizer(panGR) }
核心是手势处理代码,思路非常简单,根据用户滑动的偏移值决定切换的百分比:
func handlePan(recognizer: UIPanGestureRecognizer) { var transition = recognizer.translationInView(self.navigationVC!.view) var progress = fabs(transition.x) / self.navigationVC!.view.bounds.size.width switch recognizer.state { case .Began: self.interactionController = UIPercentDrivenInteractiveTransition() if self.navigationVC?.viewControllers.count > 1 { self.navigationVC?.popViewControllerAnimated(true) } else { self.navigationVC!.topViewController.performSegueWithIdentifier("PushSegue", sender: nil) } case .Changed: self.interactionController?.updateInteractiveTransition(progress) case .Ended: if fabs(recognizer.velocityInView(recognizer.view).x) > 0 { self.interactionController?.finishInteractiveTransition() } else { self.interactionController?.cancelInteractiveTransition() } self.interactionController = nil default: self.interactionController?.cancelInteractiveTransition() self.interactionController = nil } }
最后,在代理方法中返回我们创建的这个UIPercentDrivenInteractiveTransition对象:
func navigationController(navigationController: UINavigationController, interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { return self.interactionController }
这样手势操作就完成了,效果还是不错的
参考资料
本文是对Raywenderlich的文章
http://www.raywenderlich.com/86521/how-to-make-a-view-controller-transition-animation-like-in-the-ping-app
的学习总结,部分图片也来自该文章。特此感谢~
- iOS开发——圆形过渡动画
- iOS动画技术——iOS 7自定义过渡动画
- iOS开发中CATransition过渡动画的类型
- (0034) iOS 开发之UIView动画(过渡效果)
- IOS页面切换过渡动画
- iOS 过渡动画的实现
- CSS3动画——transition(过渡)
- Android动画 —— Activity过渡
- android 5.x—过渡动画Transition
- iOS学习之——自定义过渡动画的实现和使用
- 牢骚发完了,还要继续,android 圆形的过渡动画
- Android动画之5.0——过渡动画
- iOS开发圆形进度条
- ios uivew过渡动画 翻页效果
- iOS仿微信相册界面翻转过渡动画
- iOS仿微信相册界面翻转过渡动画
- 自定义iOS的过渡动画的效果
- iOS仿微信相册界面翻转过渡动画
- 欢迎使用CSDN-markdown编辑器
- Clone Graph
- HDU 1829 A Bug's Life
- 【android】类似微信底部按钮标签实现
- HDU 1253 胜利大逃亡
- iOS开发——圆形过渡动画
- HDU 1969 Pie
- Linux多线程实践(4) --线程特定数据
- 欢迎使用CSDN-markdown编辑器
- UVa 11174 Stand in a Line
- CodeForces 225C Barcode
- Shell文本处理 (2). cut、sort、wc、uniq、tr、 join、paste、 split、xargs
- 【Uva 1583】 Digit Generator
- 大数据学习经验总结