iOS7 開始蘋果推出了自定義轉場的 API 。從此,任何可以用 CoreAnimation 實現的動畫,都可以出現在兩個 ViewController 的切換之間。并且實現方式高度解耦,這也意味著在保證代碼干凈的同時想要替換其他動畫方案時只需簡單改一個類名就可以了,真正體會了一把高顏值代碼帶來的愉悅感。
其實網上關于自定義轉場動畫的教程很多,這里我是希望同學們能易懂,易上手。
轉場分兩種Push和Modal,所以自定義轉場動畫也就肯定分兩種,今天我們講的是Push
自定義轉場動畫Push
想在Push的時候實現自定義轉場動畫首先要遵守一個協議UINavigationControllerDelegate
蘋果在 UINavigationControllerDelegate 中給出了幾個協議方法,通過返回類型就可以很清楚地知道各自的具體作用。
//用來自定義轉場動畫
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
//為這個動畫添加用戶交互
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0);
在第一個方法里只要返回一個準守UIViewControllerInteractiveTransitioning協議的對象,并在里面實現動畫即可
創建繼承自 NSObject 并且聲明 UIViewControllerAnimatedTransitioning 的的動畫類。
重載 UIViewControllerAnimatedTransitioning 中的協議方法。
//返回動畫時間
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
//將動畫的代碼寫到里面即可
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
首先我自定義一個名為CustomAnimateTransitionPush的類繼承于NSObject準守了UIViewControllerAnimatedTransitioning協議
self.transitionContext=transitionContext;
// 獲取動畫的源控制器和目標控制器
ViewController * fromVC = (ViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
PushViewController *toVC = (PushViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *contView = [transitionContext containerView];
UIButton *button = fromVC.button;
UIBezierPath *maskStartBP = [UIBezierPath bezierPathWithOvalInRect:button.frame];
// 都添加到container中。注意順序
[contView addSubview:fromVC.view];
[contView addSubview:toVC.view];
//*******************************下面代碼就是自定義動畫了大家把想要實現的動畫寫在下面即可**********************//
//創建兩個圓形的 UIBezierPath 實例;一個是 button 的 size ,另外一個則擁有足夠覆蓋屏幕的半徑。最終的動畫則是在這兩個貝塞爾路徑之間進行的
CGPoint finalPoint;
//判斷觸發點在那個象限
if(button.frame.origin.x > (toVC.view.bounds.size.width / 2)){
if (button.frame.origin.y < (toVC.view.bounds.size.height / 2)) {
//第一象限
finalPoint = CGPointMake(button.center.x - 0, button.center.y - CGRectGetMaxY(toVC.view.bounds)+30);
}else{
//第四象限
finalPoint = CGPointMake(button.center.x - 0, button.center.y - 0);
}
}else{
if (button.frame.origin.y < (toVC.view.bounds.size.height / 2)) {
//第二象限
finalPoint = CGPointMake(button.center.x - CGRectGetMaxX(toVC.view.bounds), button.center.y - CGRectGetMaxY(toVC.view.bounds)+30);
}else{
//第三象限
finalPoint = CGPointMake(button.center.x - CGRectGetMaxX(toVC.view.bounds), button.center.y - 0);
}
}
CGFloat radius = sqrt((finalPoint.x * finalPoint.x) + (finalPoint.y * finalPoint.y));
UIBezierPath *maskFinalBP = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, -radius, -radius)];
//創建一個 CAShapeLayer 來負責展示圓形遮蓋
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = maskFinalBP.CGPath; //將它的 path 指定為最終的 path 來避免在動畫完成后會回彈
toVC.view.layer.mask = maskLayer;
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.fromValue = (__bridge id)(maskStartBP.CGPath);
maskLayerAnimation.toValue = (__bridge id)((maskFinalBP.CGPath));
maskLayerAnimation.duration = [self transitionDuration:transitionContext];
maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
maskLayerAnimation.delegate = self;
[maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
//*******************************上面代碼就是自定義動畫了大家把想要實現的動畫寫在上面即可**********************//
在控制器里面用來自定義轉場動畫的方法里返回剛才自定義的動畫類
-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if(operation==UINavigationControllerOperationPush)
{
//自定義的動畫類
CustomAnimateTransitionPush *animateTransitionPush=[CustomAnimateTransitionPush new];
return animateTransitionPush;
}
else
{
return nil;
}
}
到此為止自定義轉場動畫就完成了
pop的動畫只是把push動畫反過來做一遍這里就不細講了,有疑問的可以去看代碼
上面說到這個方法是為這個動畫添加用戶交互的所以我們要在pop時實現滑動返回
最簡單的方式應該就是利用UIKit提供的UIPercentDrivenInteractiveTransition類了,這個類已經實現了UIViewControllerInteractiveTransitioning協議,同學men可以通過這個類的對象指定轉場動畫的完成百分比。
//為這個動畫添加用戶交互
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0);
第一步 添加手勢
UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[self.view addGestureRecognizer:gestureRecognizer];
第二步 通過用戶滑動的變化確定動畫執行的比例
- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer {
/*調用UIPercentDrivenInteractiveTransition的updateInteractiveTransition:方法可以控制轉場動畫進行到哪了,
當用戶的下拉手勢完成時,調用finishInteractiveTransition或者cancelInteractiveTransition,UIKit會自動執行剩下的一半動畫,
或者讓動畫回到最開始的狀態。*/
if([gestureRecognizer translationInView:self.view].x>=0)
{
//手勢滑動的比例
CGFloat per = [gestureRecognizer translationInView:self.view].x / (self.view.bounds.size.width);
per = MIN(1.0,(MAX(0.0, per)));
if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
[self.navigationController popViewControllerAnimated:YES];
}else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){
[ self.interactiveTransition updateInteractiveTransition:per];
}else if (gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled){
if (per > 0.5) {
[ self.interactiveTransition finishInteractiveTransition];
}else{
[ self.interactiveTransition cancelInteractiveTransition];
}
self.interactiveTransition = nil;
}
}
}
第三步 在為動畫添加用戶交互的代理方法里返回UIPercentDrivenInteractiveTransition的實例
- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController {
return self.interactiveTransition;
}
如果感覺這篇文章對您有所幫助,順手點個喜歡,謝謝啦
代碼放在了GitHub上大家可以下載。