MDTransitioning
@copyrightModool
前言 http://blog.cocoachina.com/article/59454
转场动画就是以某种方式从一个场景以动画的形式过渡到另一个场景。
然而,动画的可变性太强,每个设计师都有自己的想法,对于开发来说,是无法用固定的模式来限制动画的实现。
其次,系统已经为我们提供了一套很好的动画模板,我们只需要实现其相关方法,就能够很容易的实现各种各样的动画。
所以,本文将要介绍的并不是转场所需要的动画,而是针对"转场过程控制"的一种设计方案,以便提供一种具有可定制,可扩展的设计方案。
为了方便集成,我们也提供了很多套可选的动画方案,比如传统的侧滑、缩放、图片预览等等。
iOS7 以前
在 iOS7 以前,系统不支持手势右划返回的,只能实现一些过度动画,如以下方案:
// Code here// Custom present[toViewController willMoveToParentViewController:self];
[fromViewController willMoveToParentViewController:nil];[selfaddChildViewController:toViewController];
[[selfview] addSubview:[toViewController view]];[selftransitionFromViewController:fromViewController toViewController:toViewController duration:0.3options:UIViewAnimationOptionTransitionCrossDissolve animations:^{// 过度效果} completion:^(BOOLfinished) { [fromViewController removeFromSuperView];
[fromViewController removeFromParentViewController];
[fromViewController didMoveToParentViewController:nil];
[toViewController didMoveToParentViewController:self];}];
或者
// Code here
[navigationController pushViewController:toViewController animated:NO];[UIViewanimateWithDuration:0.3options:UIViewAnimationOptionTransitionCrossDissolve animations:^{// 过度效果} completion:nil];
iOS7以前,没有为Controller提供transitioningDelegate
// Code here@interfaceUIViewController(UIViewControllerTransitioning)
@property(nullable,nonatomic,weak)id transitioningDelegate;
@end
所以对于Present、Dismiss来说,尚且还是可以实现,而对于Push和Pop的定制非常困难,就更不要说针对转场来进行交互控制。
iOS7 以后
在 iOS7 以后,系统针对交互转场这方面出现了很大变化,不仅支持了手势交互,还提供了一套标准的转场协议和动画协议。
默认交互方案
首先,我们来看下Push和Pop,在我们不去实现任何交互,以及任何动画的时候,系统所提供给我们的交互方案:
// Code here
@interfaceUINavigationController:UIViewController// 系统默认提供的侧滑手势(从屏幕边界往右划)
@property(nullable,nonatomic,readonly) UIGestureRecognizer*interactivePopGestureRecognizer
@end
我们看到,系统默认的手势是基于UINavigationController的,也就是说,手势是加在UINavigationController的 transitionView 上的,而且是只读的,我们无法篡改手势,以及无法定制手势,当然,这其中其他的问题,我们待会再说。
我们继续再看下系统默认的Push和Pop转场定制方案:
默认转场方案
UIViewControllerInteractiveTransitioning
转场协议:虽然说是控制转场交互的,但是并没有达到字面意思上所具备动能。
UIPercentDrivenInteractiveTransition
转场进度控制器:系统默认提供的,通过百分比来控制转场的播放进度。
// Code here//
UINavigationControllerDelegate// 如果需要定制转场控制方案,需提供UIViewControllerInteractiveTransitioning实例,
// 默认情况下不需要实现此代理,- (id)navigationController:(UINavigationController*)navigationController interactionControllerForAnimationController:(MDNavigationAnimationController *)animationController
{
return[UIPercentDrivenInteractiveTransition new];
}
默认动画方案
UIViewControllerAnimatedTransitioning
动画协议:当Push或者Pop动作发生后,会通过代理获取到该协议的具体动画实例。
// Code here// UINavigationControllerDelegate// 如果需要定制动画方案,需提供UIViewControllerAnimatedTransitioning实例,// 默认情况下不需要实现此代理,- (id)navigationController:(UINavigationController*)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController*)fromViewController toViewController:(UIViewController*)toViewController {if(operation == UINavigationControllerOperationPush)returnpush animation;if(operation == UINavigationControllerOperationPop)returnpop animation;returnnil;}
我再来看下Present、Dismiss,和Push、Pop类似
// Code here- (id)animationControllerForPresentedController:(MDImageViewController *)presented presentingController:(UIViewController*)presenting sourceController:(UIViewController*)source {returnpresent transitioning;}- (id)animationControllerForDismissedController:(MDImageViewController *)dismissed {returndismiss transitioning;}- (id)interactionControllerForPresentation:(id)animator;{returnpresent interactive transitioning;}- (id)interactionControllerForDismissal:(id)animator;{returndismiss interactive transitioning;}
无论是Present、Dismiss、Push、Pop中的哪一种,都要遵从下面这个流程
由于基于系统的默认方案,我们会有以下三个问题:
可以针对每个Controller定制动画,但是由于UINavigationController,interactivePopGestureRecognizer以及transitioningDelegate的局限性,在维护上相对离散,没有一个统一的方案;
不可以针对每个Controller定制交互
不可以针对每个Controller转场控制的
对第三方动画和交互控制器的集成相对复杂
所有我们准备提供一套完整的可扩展方案来解决这几个问题
WhyMDTransitioning?
基于上面的流程图来看,倘若我们需要针对每个转场进行定制,工作相对繁琐,维护困难。
所以MDTransitioning方案基于以下几个原则:
交互:从谁那里来,就由谁提供
动画:到哪里去,就由谁提供
控制:谁触发,就由谁维护
类关系
动画类
MDViewControllerAnimatedTransitioning
基于UIViewControllerAnimatedTransitioning协议的再次封装,对关联的主从View Controller进行定义
MDNavigationAnimatedTransitioning
基于MDViewControllerAnimatedTransitioning协议的再次封装,定义Push和Pop的两种动画方式
MDNavigationAnimationController
基于MDNavigationAnimatedTransitioning协议的默认实现,默认实现Push和Pop动画,呈现方式和系统保持一致
MPresentionAnimatedTransitioning
基于MDViewControllerAnimatedTransitioning协议的再次封装,定义Present和Dismiss的两种动画方式
MDPresentionAnimationController
基于MPresentionAnimatedTransitioning协议的默认实现,默认实现Present和Dismiss动画,呈现方式和系统保持一致
交互驱动类
MDPercentDrivenInteractiveTransitioning
基于系统UIViewControllerInteractiveTransitioning协议的再次封装,定义驱动所需的常规方法
UIPercentDrivenInteractiveTransition
系统提供的默认百分比驱动器,以便后期对驱动的扩展
交互控制类
MDInteractionController
交互控制协议,负责控制交互的生命周期,手势变化,以及转场触发和进度维护
MDSwipeInteractionController
基于MDInteractionController协议的实现,实现对滑动手势交互的控制
MDPopInteractionController
基于MDSwipeInteractionController,提供Pop动作的滑动条件,以及进度计算
Pop动作控制器,默认由UIViewController实现,负责控制交互的加载条件、初始化、以及手势依赖的UIView
Present、Dismiss动作控制器,默认由UIViewController实现,负责控制Presention交互的加载条件、初始化、以及手势依赖的UIView
优点
提供一套默认的方案,和系统效果保持一致
提供所有定制方案的底层类,轻轻松松就能完成自定义交互和动画的集成
方便集成第三方动画和交互控制器,例如CE系列
重点在这里,默认方案,零写入,零入侵
到底有多简单?
案例一
MDNavigationControllerDelegate
为了方便接入,我们提供了默认的Pop和Push代理实现,只需要下面一行代码,就可以实现对系统转场的替换。
// Code herenavigationController.delegate= [MDNavigationControllerDelegate defaultDelegate];
效果和系统的保持一致
案例二
MDPresentionControllerDelegate
当然对于Present和Dismiss我们也提供了默认的代理实现,同样只需要下面一行代码对系统转场的替换。
// Code hereviewController.transitioningDelegate = [MDPresentionControllerDelegate delegateWithReferenceViewController:referenceViewController];
效果和系统的保持一致
为某个View Contorller定制一个缩放的Pop动画,但Push动画用默认的
// Code here// 在将要显示的`View Contorller`内实现该方法,默认实现返回父类结果- (id)animationForNavigationOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController*)fromViewController toViewController:(UIViewController*)toViewController;{if(operation == UINavigationControllerOperationPush) {return[superanimationForNavigationOperation:operation fromViewController:fromViewController toViewController:toViewController]; }else{return[[MDScaleNavigationAnimationController alloc] initWithOperation:operation fromViewController:fromViewController toViewController:toViewController]; }}
案例三
因为水平pop是默认实现的,所以我们调整一下,为某个View Contorller定制一个垂直滑动的Pop手势交互,虽然有点变态,但是这仅为测试
// Code here// 在将要显示的`View Contorller`中复写该方法,提供垂直滑动的交互控制器- (id)requirePopInteractionController{return[MDVerticalSwipPopInteractionController interactionControllerWithViewController:self];}
效果如图:
案例四
为某个View Contorller定制一个图片放大的Present动画,但Dismiss动画用默认抽屉式的
// Code here// 在将要显示的`View Contorller`内实现该方法,默认实现返回父类结果- (id)animationForPresentionOperation:(MDPresentionAnimatedOperation)operation fromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController;{if(operation == MDPresentionAnimatedOperationPresent) { MDImageZoomAnimationController *controller = [MDImageZoomAnimationController animationForPresentOperation:operation fromViewController:fromViewController toViewController:toViewController]; controller.hideReferenceImageViewWhenZoomIn=NO;returncontroller; }else{return[[MDPresentionAnimationController alloc] initWithOperation:operation fromViewController:fromViewController toViewController:toViewController]; }}
案例五
图片预览器,为某个View Contorller定制一个图片缩小的Dismiss手势交互
// Code here// 在将要显示的`View Contorller`的`viewDidLoad`方法中添加交互控制器- (void)viewDidLoad { [superviewDidLoad]; MDImageDraggingDismissInteractionController *interactionController = [MDImageDraggingDismissInteractionController interactionControllerWithViewController:self]; interactionController.translation=200.f;self.presentionInteractionController= interactionController;}
效果如图:
现成的动画和交互太少怎么办?
配合CE系列的动画和交互一起使用,我们提供CE的扩展方案MDTransitioning-VCTransitionsLibrary.
联系方式