iOS自定義轉場動畫(push、pop動畫)

weixin_34146805發表於2016-09-27

iOS7推出了新的轉場動畫API,以協議id<UIViewControllerInterativeTransition>、id<UIViewAnimatedTransitioning>方式開放給開發者,不同於代理、類別,這樣更易於我們自定義動畫,更加靈活。下面介紹一下自定義轉場動畫

2455861-b55c0066271dd571.gif
仿酷狗push
2455861-0bdf47402e5e012b.gif
push
要使用的協議
  • UIViewControllerInteractiveTransitioning 互動協議,主要在右滑返回時用到
  • UIViewControllerAnimatedTransitioning 動畫協議,含有動畫時間及轉場上下文兩個必須實現協議
  • UIViewControllerContextTransitioning 動畫協議裡邊的協議之一,動畫實現的主要部分
  • UIPrecentDrivenInteractiveTransition 用在互動協議,百分比控制當前動畫進度。
自定義步驟
  • 首先要實現navigation的代理,navigation有兩個返回id型別的協議,實現這兩個協議

第一個方法返回一個UIPercentDrivenInterativeTransition型別的物件即可,這個物件預設實現了UIPercentInterativeTransitioning協議,需要注意的是,這個返回值主要是用於互動動畫,也就是右滑返回時需要用到,這裡我在基類baseViewController定義了一個UIPercentDrivenInterativeTransition型別的屬性。
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController )navigationController
interactionControllerForAnimationController:(WTKBaseAnimation
) animationControlle{
return animationControlle.interactivePopTransition;
}
第二個方法我自定義了一個遵循UIViewControllerAnimationTransitioning協議的類WTKBaseAnimation,
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(WTKBaseViewController *)fromVC
toViewController:(UIViewController *)toVC{
if (fromVC.interactivePopTransition)
{
WTKBaseAnimation *animation = [[WTKBaseAnimation alloc]initWithType:operation Duration:0.6 animateType:self.animationType];
animation.interactivePopTransition = fromVC.interactivePopTransition;
return animation; //手勢
}
else
{
WTKBaseAnimation *animation = [[WTKBaseAnimation alloc]initWithType:operation Duration:0.6 animateType:self.animationType];
return animation;//非手勢
};}

第二個方法返回物件自定義如下


2455861-0b2d5589dc2a2eb2.png
baseAnimation構建方法.png

也就是需要把navigation的代理方法中的引數都傳過來。
在本類中,需要實現轉場動畫協議:
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{
return self.duration;}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
if (self.transitionType == UINavigationControllerOperationPush)
{
    [self push:transitionContext];
}
else if (self.transitionType == UINavigationControllerOperationPop)
{
    [self pop:transitionContext];
}}   

[self push:transitionContext]; [self pop:transitionContext];在本類中並沒有真正的實現,具體交給子類實現

2455861-8fd12606df70ffbe.png
Paste_Image.png

- (void)push:(id<UIViewControllerContextTransitioning>)transitionContext{}
- (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext{}

下面介紹子類的具體實現,

push

|
<pre><code>- (void)push:(id<UIViewControllerContextTransitioning>)transitionContext {

UIViewController * fromVc   = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

UIViewController * toVc     = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

NSTimeInterval duration     = [self transitionDuration:transitionContext];

CGRect bounds               = [[UIScreen mainScreen] bounds];

fromVc.view.hidden          = YES;

[[transitionContext containerView] addSubview:toVc.view];

[[toVc.navigationController.view superview] insertSubview:fromVc.snapshot belowSubview:toVc.navigationController.view];

toVc.navigationController.view.transform = 

CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0);

[UIView animateWithDuration:duration
                      delay:0
     usingSpringWithDamping:1.0
      initialSpringVelocity:0
                    options:UIViewAnimationOptionCurveLinear
                 animations:^{
                     fromVc.snapshot.transform = CGAffineTransformMakeTranslation(-CGRectGetWidth(bounds) * 0.3, 0);
                     toVc.navigationController.view.transform = CGAffineTransformMakeTranslation(0, 0);
                 }
                 completion:^(BOOL finished) {
                     fromVc.view.hidden = NO;
                     [fromVc.snapshot removeFromSuperview];
                     [transitionContext completeTransition:YES];
}];
}

</code></pre>
其中fromVC與toVC、為函式引數transitionContext協議獲得,duration呼叫父類方法獲得,最終為navigation的代理方法中返回的時間,也可以自定義。fromVC為原來的ViewController,toVC為要push的VC
首先將fromVC的view隱藏,使用VC的snapshot代替,snapshot為viewController的截圖,這裡使用類別關聯屬性實現的。
[transitionContext containerView] 為容器,存轉場需要的view,
分別將toVC.view及fromVC.snapshot新增到容器中,注意新增順序、view存放的順序。

下面將toVC移動到螢幕右邊,這裡使用的是改變transform,

使用UIView做動畫,需要注意的是,使用usingSpringWithDamping的動畫,關於這個動畫不再多說。
UIView動畫,需要把fromVC移動到左邊(移動多少可自定),toVC移動到右邊。
動畫完成後,需要把原來隱藏的fromVC.view顯示,新增到容器的view移除,當前顯示的vc不需要移除。
** 最後需要呼叫轉場完成方法** [transitionContext completeTransition:YES];

Pop

|

  - (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext {

WTKBaseViewController * fromVc  = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

UIViewController * toVc         = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

NSTimeInterval duration         = [self transitionDuration:transitionContext];

CGRect bounds                   = [[UIScreen mainScreen] bounds];

[fromVc.view addSubview:fromVc.snapshot];
fromVc.navigationController.navigationBar.hidden = YES;
fromVc.view.transform = CGAffineTransformIdentity;

toVc.view.hidden                = YES;
toVc.snapshot.transform         = CGAffineTransformMakeTranslation(-CGRectGetWidth(bounds) * 0.3, 0);

[[transitionContext containerView] addSubview:toVc.view];
[[transitionContext containerView] addSubview:toVc.snapshot];
[[transitionContext containerView] sendSubviewToBack:toVc.snapshot];

if (fromVc.interactivePopTransition)
{
    [UIView animateWithDuration:duration
                          delay:0
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{
                         fromVc.view.transform = CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0.0);
                         toVc.snapshot.transform = CGAffineTransformIdentity;
                     }
                     completion:^(BOOL finished) {

                         toVc.navigationController.navigationBar.hidden = NO;
                         toVc.view.hidden = NO;

                         [fromVc.snapshot removeFromSuperview];
                         [toVc.snapshot removeFromSuperview];
                         fromVc.snapshot = nil;

                         if (![transitionContext transitionWasCancelled]) {
                             toVc.snapshot = nil;
                         }

                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];

}
else
{
    [UIView animateWithDuration:duration
                          delay:0
         usingSpringWithDamping:1
          initialSpringVelocity:0
                        options:UIViewAnimationOptionCurveLinear
                     animations:^{
                         fromVc.view.transform = CGAffineTransformMakeTranslation(CGRectGetWidth(bounds), 0.0);
                         toVc.snapshot.transform = CGAffineTransformIdentity;
                     }
                     completion:^(BOOL finished) {

                         toVc.navigationController.navigationBar.hidden = NO;
                         toVc.view.hidden = NO;

                         [fromVc.snapshot removeFromSuperview];
                         [toVc.snapshot removeFromSuperview];
                         fromVc.snapshot = nil;

                         if (![transitionContext transitionWasCancelled]) {
                             toVc.snapshot = nil;
                         }

                         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
                     }];
}}

pop方法與push類似,不再多說,需要注意的是** 一定要區分手勢和非手勢 **,也就是如果點選按鈕返回,需要使用usingSpringWithDamping動畫,右滑返回不使用這個。
判斷方式fromVc.interactivePopTransition,這個為在基類baseViewController裡邊自定義的一個UIPercentDrivenInterativeTransition型別的屬性,也就是navigation代理方法- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(WTKBaseAnimation*) animationControlle的返回值。

  • 在baseViewController中新增手勢:UIPanGestureRecognizer,新增到self.view上面,viewDidLoad中如下:

if (self.navigationController && self != self.navigationController.viewControllers.firstObject)
{
    UIPanGestureRecognizer *popRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(handlePopRecognizer:)];
    [self.view addGestureRecognizer:popRecognizer];
    popRecognizer.delegate = self;
}

在手勢方法中建立UIPercentInterativeTransition,在拖動過程中,用這個例項變數呼叫updateInteractiveTransition方法,程式碼如下


- (void)handlePopRecognizer:(UIPanGestureRecognizer *)recognizer{
CGFloat progress = [recognizer translationInView:self.view].x / CGRectGetWidth(self.view.frame);
progress = MIN(1.0, MAX(0.0, progress));
NSLog(@"progress---%.2f",progress);
if (recognizer.state == UIGestureRecognizerStateBegan)
{
    self.interactivePopTransition = [[UIPercentDrivenInteractiveTransition alloc]init];
    [self.navigationController popViewControllerAnimated:YES];
}
else if (recognizer.state == UIGestureRecognizerStateChanged)
{
    [self.interactivePopTransition updateInteractiveTransition:progress];
}
else if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled)
{
    if (progress > 0.25)
    {
        [self.interactivePopTransition finishInteractiveTransition];
    }
    else
    {
        [self.interactivePopTransition cancelInteractiveTransition];
    }
    self.interactivePopTransition = nil;
}}

上面定義的progress,為了記錄滑動的百分比,隨時更新interactivePopTransition 當手勢結束,根據progress判斷當前是否可以pop回來,這裡是以0.25為標準。

程式碼連線 git連線

相關文章