在這個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