前言
平时我们使用的 push、pop、present、dismiss动画,无非就是系统默认的推进、推出效果,model动画还好点,有四种效果,navigation就只有一种效果了
如果想要使用适合场景的自定义动画,那么就需要自定义了,例如:微信的查看大图,一些变换特效等
自定义转场动画
model转场动画特有协议
model转场动画,也就是常见的 present、dismiss,设置其转场动画要实现下面两个特有的方法
注意:其应当是一个转场动画一个 UIViewControllerAnimatedTransitioning实现类(后面介绍),demo为了方便,提取出来了一个共用类,所以略有不同
//present动画的设置方法
//返回实现 UIViewControllerAnimatedTransitioning 协议的对象(后面介绍)
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented
presentingController:(UIViewController *)presenting
sourceController:(UIViewController *)source;
//dissmiss动画的设置方法
//返回实现 UIViewControllerAnimatedTransitioning 协议的对象(后面介绍)
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
navigation转场动画特有协议
navigation转场动画,也就是常见的 push、pop,设置其转场动画要实现下面两个特有的方法
注意:其应当是一个转场动画一个 UIViewControllerAnimatedTransitioning实现类(后面介绍),demo为了方便,提取出来了一个共用类,所以略有不同
//push、pop共用的一个动画设置方法
//返回实现 UIViewControllerAnimatedTransitioning 协议的对象(后面介绍)
//动画的类型(push、pop)可通过operation枚举类型来区分,可以看下面枚举
//可以根据需动画要使用
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC;
operation如下所示,为一个转场动画的操作类型,一般为push或pop
typedef NS_ENUM(NSInteger, UINavigationControllerOperation) {
UINavigationControllerOperationNone,
UINavigationControllerOperationPush,
UINavigationControllerOperationPop,
};
通用转场动画协议(UIViewControllerAnimatedTransitioning)
自定义转场动画要实现的核心协议便是UIViewControllerAnimatedTransitioning,无论是 mode还是navigation都要实现此协议,其经常使用的方法有下面两个:
//设置动画执行时间
- (NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)
transitionContext;
//自定义动画的实现方法,transitionContext里面有实现动画所需要的相关变量
- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext
设置动画时间的不说,animateTransition:才是我们编写转场动画的方法,下面先给一个案例,然后再介绍一下里面的其他属性
- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext {
//获取变化前的控制器,push前的顶部控制器(pop相反)
UIViewController *fromVc = [transitionContext
viewControllerForKey:UITransitionContextFromViewControllerKey];
//获取变化后的控制器,push后的顶部控制器(pop相反)
UIViewController *toVc = [transitionContext
viewControllerForKey:UITransitionContextToViewControllerKey];
//用与变换的containerView,也是最底部的view,需要主动添加toVc.view到上面去
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toVc.view];
//采用UIView动画变换图层
[UIView
transitionFromView:fromVc.view
toView:toVc.view
duration:[self transitionDuration:transitionContext]
options:UIViewAnimationOptionTransitionFlipFromLeft
completion:^(BOOL finished) {
//完成过渡动画后,标记变换结束
[transitionContext completeTransition:YES];
}];
}
上面就是一个 Flip的动画了,可以应用到 mode和navigation中,相信看到了也觉得很简单,下面循序,继续介绍
实现了 UIViewControllerContextTransitioning协议的 transitionContext有哪些常用方法呢
containerView: 当前展示的底部视图,需要将变换后的图层(ToViewController)添加上去,否则不显示当前控制器图层,在变换图层的过程中,可以通过添加和移除新图层,来完成复杂一些的过渡动画
completeTransition:: 完成动画后,标记变换结束,以完善该流程的系统的其他处理
viewControllerForKey:: 通过 transitionContext参数,可以通过该方法,加上一些key来获取 fromViewController和toViewController,即push、pop等前后两个视图控制器,一共有两个key:UITransitionContextFromViewControllerKey、UITransitionContextToViewControllerKey,相信看名字就知道表达的意义,分别为变换前后的控制器 from -> to
viewForKey:: 与 viewControllerForKey类似,获取控制器视图的参数,返回 UIView类型的参数,分别为:UITransitionContextFromViewKey、UITransitionContextToViewKey,分别为变换前后的控制器内部view
initialFrameForViewController、finalFrameForViewController: 用于参考的两个参数,为控制器变换前后的 frame 信息,用于作为动画参考,或者动画处理时使用
animationEnded:push、pop是否应用了动画,用于动画控制,可以不使用该参数
前面介绍了containerView是展示界面UI底部视图,在进行动画时,我们需要将 toViewController的view视图添加上去,这样新页面才会正常显示
因此我们在 containerView上面添加的视图不会消失,在进行动画处理的时候需要注意
将containerView与截图大法 snapshotViewAfterScreenUpdates:结合使用可以制作比较复杂的动画效果,可是使原图层免受动画变换的干扰
//可以将指定图截图绘制成生成一个新的View图层,可用于动画图层变换显示
//可以理解为一张 imageView 图片
UIView *fromView = [fromVc.view snapshotViewAfterScreenUpdates:YES];
UIView *toView = [toVc.view snapshotViewAfterScreenUpdates:YES];
通过上面 snapshotViewAfterScreenUpdates 方法生成的 view,当做变换图层时使用的临时view变换,以用来制作复杂动画,而不影响真实的 viewController图层
截图大法制作动画原理:可以理解在真实的原始图层上面,加上一个一模一样的图层,对上面的图层各种变换,最后变换成和在原始图层一样的样式,随后瞬间移除,这样用户看到的效果就和变换原始真实图层效果一样了
下面是使用截图大法制作的一个类似 push的推进效果,从截图那一步开始,其实就算是为了动画做准备了
另外,如果复杂动画用到了 CoreAnimation 相关 api,可以使用其代理,来合理监听动画的开始和结束
UIViewController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//用与变换的containerView,也是最底部的view,需要主动添加toVc.view到上面去
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toVc.view];
//根据原始图层生成新的截图,以便于过渡动画的制作
UIView *fromView = [fromVc.view snapshotViewAfterScreenUpdates:YES];
UIView *toView = [toVc.view snapshotViewAfterScreenUpdates:YES];
//获取视图宽高,可以使用finalFrameForViewControllerc等获取frame来处理,这里为了方便就直接使用了
CGFloat width = fromView.frame.size.width;
CGFloat height = fromView.frame.size.height;
//设置动画过渡前的 viewframe 状态,可以缩放(rotation),渐变(alpha)
fromView.frame = CGRectMake(0, 0, width, height);
toView.frame = CGRectMake(-width, 0, width, height);
//将截图获得过渡动画图层,添加到 containerView上面,以显示过渡动画
[containerView addSubview:fromView];
[containerView addSubview:toView];
//开始过渡动画
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromView.frame = CGRectMake(width, 0, width, height);
toView.frame = CGRectMake(0, 0, width, height);
} completion:^(BOOL finished) {
//过渡动画结束后,移除后添加的两个过渡动画图层view
[fromView removeFromSuperview];
[toView removeFromSuperview];
[transitionContext completeTransition:YES];
}];
//使用animation的时候需要设置代理,这样就可以得到动画开始结束的时机了
// CABasicAnimation *basic;
// basic.delegate = self;
// - (void)animationDidStart:(CAAnimation *)anim;
// - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag;
通过可以看到使用截图大法,大致经过了下面 3 步:
1、获取控转场动画变换前后的视图控制器 fromViewController、toViewController
2、将 toViewController 的view添加到 containerView上面,以显示变换后的控制器图层,到这里,如果没有动画,可以理解为已经结束了(只需要在调用completeTransition即可)
3、这里我们就是开始动画的制作了,简单的动画,可以直接使用原图层进行变换操作即可,复杂的可以使用截图大法来做
3.1 普通动画制作,使用的前提是不会对原始图层干扰,直接使用原始图层,直接使用系统的动画方法进行变换,动画结束后,使用completeTransition 标记变换结束
3.2 复杂动画制作,万金油,无论对原始图层干扰与否,都可以使用,只是逻辑上稍微复杂一些,越复杂的动画处理起来逻辑越多
3.2.1 复杂动画第一步,先进行截图处理,使用 snapshotViewAfterScreenUpdates方法,获取新的图层,然后添加到 containerView上面
3.2.2 在设置动画之前样式,如果是平移推进,那么就处理好位置;如果是缩放,那么就提前设置好缩放的点以及倍率,开始之前的样式,一定要和 fromViewContoller的原始样式相同
3.2.3 设置动画过渡之后的样式,其过渡之后的样式一样要为 toViewController的原始样式,无论位置还是大小等,都要一样(如果过渡动画比较麻烦,可能中间要分为好几步骤,无论怎样,开始和结束的样式都要与原始样式相同)
3.2.4 过渡动画完成之后,需要移除后添加到 containerView上面的复制图层,然后调用 completeTransition 标记变换结束
最后
对于pop上一页的手势问题,可以参考上一篇文章:navigation与手势的一些问题
快来尝试一些自定义转场动画吧,不妨做一个仿微信的图片放大效果吧