在这个demo中,核心为选用画布CAShapeLayer,由于通常都是用它来处理形状之类的动画,结合了贝塞尔曲线来控制路径,而后使用CABasicAnimation核心动画来产生全部效果。动画
///动画自定义封装 -(void)animationWithView:(UIView *)view{ //1.建立layer CAShapeLayer *layer = [[CAShapeLayer alloc]init]; //2.建立贝塞尔路径(参数为圆的外接矩形) //间距 CGFloat margin = 20; //半径 CGFloat radius = 25; //屏幕尺寸 CGFloat viewWidth = [UIScreen mainScreen].bounds.size.width; //屏幕高度 CGFloat viewHeight = [UIScreen mainScreen].bounds.size.height; //屏幕对角线 CGFloat endRadius =sqrt(viewHeight*viewHeight +viewWidth*viewWidth); //起始路径 CGRect startRect = CGRectMake(viewWidth-2*radius-margin, margin, radius*2, radius*2); UIBezierPath *startPath = [UIBezierPath bezierPathWithOvalInRect:startRect]; //终止路径 UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(startRect, -endRadius, -endRadius) ]; //3.设置贝塞尔属性 //填充颜色 layer.fillColor = [UIColor redColor].CGColor; //4.将贝塞尔做为layer的路径 layer.path = startPath.CGPath; //将layer做为父视图的遮罩图层. view.layer.mask = layer; //5.将path添加到视图中 //[self.view.layer addSublayer:layer]; //使用核心动画实现 CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"]; //这个属性是判断是动画以前仍是动画以后的。 if (self.isPresent) { animation.fromValue = (__bridge id _Nullable)(startPath.CGPath); animation.toValue = (__bridge id _Nullable)(endPath.CGPath); }else{ animation.fromValue = (__bridge id _Nullable)(endPath.CGPath); animation.toValue = (__bridge id _Nullable)(startPath.CGPath); } animation.delegate = self; //设置动画属性 animation.fillMode = kCAFillModeForwards; animation.duration = 2; animation.removedOnCompletion = NO; //添加动画到图层 [layer addAnimation:animation forKey:nil]; }
! 这里要注意这个mask的属性,设置以后就不须要再额外的add进去,它是一种用于遮罩视图的效果,而且设置的颜色会失效spa
在这个动画中,有三个重要的属性,号称“转场三剑客”。代理
UIViewControllerAnimatedTransitioning,主要负责转场的动画时间和动画具体内容。code
#pragma mark - UIViewControllerAnimatedTransitioning ///转场动画时间 - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{ return 2; } ///转场动画的内容 - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{ //1.获取上下文的容器视图 UIView *containerView = transitionContext.containerView; //2.获取目标视图 UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; //3.将目标视图添加到容器视图 UIView *aniView = self.isPresent?toView:fromView; [containerView addSubview:aniView]; //4.开始动画 [self animationWithView:aniView]; self.context = transitionContext; }
CAAnimationDelegate,主要负责监控动画开始和动画结束以后。生命周期
#pragma mark - CAAnimationDelegate ///动画开始 - (void)animationDidStart:(CAAnimation *)anim{ } ///每次动画结束调用 - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{ //5.告诉上下文完成转场,不然上下文将会一直等待系统通知是否完成. [self.context completeTransition:YES]; }
UIViewControllerTransitioningDelegate,主要负责告诉系统由哪一个控制器提供转场,哪一个控制器来解除转场。rem
#pragma mark - UIViewControllerTransitioningDelegate ///告诉由谁提供转场 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{ self.isPresent = YES; return self; } ///由谁解除转场 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{ self.isPresent = NO; return self; }
最后只须要在须要转场的控制器中使用这个封装的类便可animation
-(void)awakeFromNib{ [super awakeFromNib]; //1.设置跳转样式 self.modalPresentationStyle = UIModalPresentationCustom; //2.设置代理 self.animation = [[JanCustomAnimation alloc]init]; self.transitioningDelegate = self.animation; } -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self dismissViewControllerAnimated:YES completion:nil]; }
须要注意的是设置样式和代理,必需要优先于viewdidload以前设置,由于这里涉及到控制器的生命周期的问题。it
好了,到这里就能够实现完整的自定义转场的酷炫效果了。io