在前面的章节中,我已经介绍了自定义过度动画的基本使用,大家可以参照这里:动画特效十三:自定义过度动画之基本使用。这一节我使用更详细的例子来阐述它的使用及引申出一些其他知识点。效果图如下:
注意到,上面两个Demo中均是自定义导航控制器切换的效果。由于实现原理相似,我只对第一个效果进行说明。在具体分析之前,我们先看一张结构图,它详细的描述了实现自定义导航控制器效果的具体步骤。
看起来很复杂的样子,现在我们就用第一个Demo进行上述方法的说明。
Step One: 主控制器遵循 UINavigationControllerDelegate 协议。
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; self.navigationController.delegate = self; }
Step Two: 实现下面的代理方法,完成自定义的Push或者Pop操作。
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
Step Three: 自定义遵守了id<UIViewControllerAnimatedTransitioning>>协议的对象,处理自定义的过度动画。假设自定义的类为 LFPushAnimator, 则"Step Two"中的代码如下:
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC { if (operation == UINavigationControllerOperationPush) { LFPushAnimator *animator = [[LFPushAnimator alloc] init]; return animator; } else { return nil; } }而遵循了UIViewControllerAnimatedTransitioning这个协议的类需要实现两个代理方法,来处理具体的自定义过度动画。
Step Four: 处理自定义的过度动画。
@interface LFPushAnimator() @property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext; @property (nonatomic, strong) CAShapeLayer *maskLayer; @end @implementation LFPushAnimator - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext { return 2.0f; } - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext { self.transitionContext = transitionContext; UIView *containerView = [transitionContext containerView]; FirstViewController *fromVC = (FirstViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; SecondViewController *toVC = (SecondViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; [containerView addSubview:fromVC.view]; [containerView addSubview:toVC.view]; UIButton *button = fromVC.pushButton; UIBezierPath *startPath = [UIBezierPath bezierPathWithOvalInRect:button.frame]; CGFloat centerToLeft = button.center.x; CGFloat centerToBottom = toVC.view.frame.size.height - button.center.y; CGFloat radius = sqrtf(centerToLeft * centerToLeft + centerToBottom * centerToBottom); // CGRectInset的计算公式 /* r1.origin.x+=dx;//dx为正数是+=,负数则-= r1.size.width-=dx*2; r1.origin.y+=dy;//dy为正数是+=,负数则-= r1.size.height-=dy*2; */ UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, -radius, -radius)]; self.maskLayer = [CAShapeLayer layer]; // 设置最终的path为endPath,不然会反弹 self.maskLayer.path = endPath.CGPath; toVC.view.layer.mask = self.maskLayer; CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"path"]; anim.duration = [self transitionDuration:transitionContext]; anim.fromValue = (__bridge id _Nullable)(startPath.CGPath); anim.toValue = (__bridge id _Nullable)(endPath.CGPath); anim.delegate = self; anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; [self.maskLayer addAnimation:anim forKey:@"animation"]; } - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { //告诉 iOS 这个 transition 完成 [self.transitionContext completeTransition:![self.transitionContext transitionWasCancelled]]; [self.maskLayer removeAnimationForKey:@"animation"]; [self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view.layer.mask = nil; } @end
1. 上面的两个红色的圆是同心圆。
2. 动画过程中是从小圆慢慢扩散到大圆。
3. 扩散过程中被圆覆盖的地方就会透明化,然后就会显示出后面的内容,让人感觉上面一层图片是被"掀开的"。
Step Five:实现手动拖拽图片的效果。
1. 定义一个比例过度的属性,用户进行后面的控制操作。
@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *percentTransition;
- (void)viewDidLoad { [super viewDidLoad]; self.view.frame = [UIScreen mainScreen].bounds; UIScreenEdgePanGestureRecognizer *edgePan = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgeEvent:)]; edgePan.edges = UIRectEdgeLeft; [self.view addGestureRecognizer:edgePan]; }
- (void)edgeEvent:(UIScreenEdgePanGestureRecognizer *)edgePan { CGFloat transitionX = [edgePan translationInView:self.view].x; CGFloat percent = transitionX / self.view.frame.size.width; percent = MIN(1.0, MAX(0.0, percent)); if (edgePan.state == UIGestureRecognizerStateBegan) { self.percentTransition = [[UIPercentDrivenInteractiveTransition alloc] init]; // 这里结合使用 [self.navigationController popViewControllerAnimated:YES]; } else if (edgePan.state == UIGestureRecognizerStateChanged) { [self.percentTransition updateInteractiveTransition:percent]; } else if (edgePan.state == UIGestureRecognizerStateEnded || edgePan.state == UIGestureRecognizerStateCancelled) { if (percent < 0.3) { [self.percentTransition cancelInteractiveTransition]; } else { [self.percentTransition finishInteractiveTransition]; } } else { self.percentTransition = nil; } }