iOS自定義轉場動畫

iOS自定義轉場動畫

本文主要簡單講解iOS中的自定義轉場動畫,也主要是源於自己在開發過程中,自己的一點感悟。其實,轉場使我們開發中經常遇到的事情,大多數情況下,我們使用模態顯示或者UINavigationController的push和pop操作實現轉場,不過這兩種形式的轉場效果是固定的,沒法自定義轉場動畫,因此,如果需要實現自定義動畫效果,這些顯然很難實現。

UIView基於block實現的轉場動畫

其實,UIView也提供了可以轉場的方法,其實下面這幾個方法也可以實現轉場,根據UIViewAnimationOptions的不同,也可以實現不同的轉場動畫,也可以算是一種簡單的自定義轉場,具體的用法大家可以查閱官方文檔。

+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay usingSpringWithDamping:(CGFloat)dampingRatio initialSpringVelocity:(CGFloat)velocity options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(7_0);

+ (void)transitionWithView:(UIView *)view duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^ __nullable)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);

+ (void)transitionFromView:(UIView *)fromView toView:(UIView *)toView duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // toView added to fromView.superview, fromView removed from its superview

使用這種block的形式,其實最大的好處就是比較直觀,轉場需要的元素都能夠清晰的看見,比如:fromView,toView,duration,animation等,但是也有一個缺陷,就是重用性不高,基本每次轉場都需要重新寫一遍,再者,也不能真正的實現自定義動畫,畢竟,options的選擇是有限的,動畫效果就那幾種。

From和To的概念

通過上面的方法,我們反覆看見fromView和toView,這個其實很好理解,fromView也就是當前視圖,toView也就是需要跳轉的視圖。如果是從A視圖控制器present到B,則A是from,B是to。從B視圖控制器dismiss到A時,B變成了from,A是to,如下:

這裏寫圖片描述

自定義present轉場動畫

首先,需要簡單介紹幾個概念:

1.The Transitioning Delegate

要想實現自定的轉場效果,首先必須理解transitionDelegate的概念,以往的模態顯示時候,我們沒有關注這個,因爲我們不需要實現自定轉場效果,如果要實現自定義轉場動畫效果,需要藉助transitionDelegate提供的方法:

@optional
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;

- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;

- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;

- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;

- (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source NS_AVAILABLE_IOS(8_0);

這裏的方法還是比較容易理解的,我們就是需要自己實現這些方法,來實現自定義動畫效果。

2.Animator objects

在animator中我們可以自定義動畫的實現效果,轉場動畫長什麼樣子,就要看我們怎麼去實現animator,在其中我們要控制動畫時間和動畫邏輯。

3.Interactive animator objects

這個跟animator一樣,只是適用交互式動畫,主要就是根據手勢動作控制轉場的變化。

4.Presentation controller

它可以對present過程更加徹底的自定義,比如修改被展示視圖的大小,新增自定義視圖等,也可以自定實現。

下圖詳細介紹了transitioning delegate和animator objects,以及presentation controller之間的層級關係,
這裏寫圖片描述

實現過程

1.創建代理

如果是從A present 到B,可以使用A 或者 B 實現UIViewControllerTransitioningDelegate,

2.設置代理

需要將1中實現delegate的viewController賦值給B的transitioningDelegate。

3.調用Present方法

調用presentViewController:animated:completion:並把參數animated設置爲true

4.提供Animator

在實現UIViewControllerTransitioningDelegate的方法中,需要提供實現動畫的animator

具體實現如下:

這裏是跳轉的相關設置

- (IBAction)toMyView:(UIButton *)sender {
    self.myController.modalPresentationStyle = UIModalPresentationCustom;
    //設置代理
    self.myController.transitioningDelegate = self.myController;
    [self presentViewController:self.myController animated:YES completion:nil];
}

接下來最終的就是提供實現具體轉場動畫的animatior:這裏只提供了一個animator,讀者也可以根據present和dismiss實現不同的animator。

#import "MyAnimator.h"

@implementation MyAnimator

- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{
    return [transitionContext isAnimated] ? 0.5 : 0.5;

}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
    // Get the set of relevant objects.
    UIView *containerView = [transitionContext containerView];
    UIViewController *fromVC = [transitionContext
                                viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC   = [transitionContext
                                viewControllerForKey:UITransitionContextToViewControllerKey];

    UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];

    // Set up some variables for the animation.
    CGRect containerFrame = containerView.frame;
    CGRect toViewStartFrame = [transitionContext initialFrameForViewController:toVC];
    CGRect toViewFinalFrame = [transitionContext finalFrameForViewController:toVC];
    CGRect fromViewFinalFrame = [transitionContext finalFrameForViewController:fromVC];

    // Set up the animation parameters.
    if (self.isPresented) {
        // Modify the frame of the presented view so that it starts
        // offscreen at the lower-right corner of the container.
        toViewStartFrame.origin.x = containerFrame.size.width;
        toViewStartFrame.origin.y = containerFrame.size.height;
    }
    else {
        // Modify the frame of the dismissed view so it ends in
        // the lower-right corner of the container view.
        fromViewFinalFrame = CGRectMake(containerFrame.size.width,
                                        containerFrame.size.height,
                                        toView.frame.size.width,
                                        toView.frame.size.height);
    }

    // Always add the "to" view to the container.
    // And it doesn't hurt to set its start frame.
    [containerView addSubview:toView];
    toView.frame = toViewStartFrame;

    // Animate using the animator's own duration value.
    [UIView animateWithDuration:[self transitionDuration:transitionContext]
                     animations:^{
                         if (self.isPresented) {
                             // Move the presented view into position.
                             [toView setFrame:toViewFinalFrame];
                         }
                         else {
                             // Move the dismissed view offscreen.
                             [fromView setFrame:fromViewFinalFrame];
                         }
                     }
                     completion:^(BOOL finished){
                         BOOL success = ![transitionContext transitionWasCancelled];

                         // After a failed presentation or successful dismissal, remove the view.
                         if ((self.isPresented && !success) || (!self.isPresented && success)) {
                             [toView removeFromSuperview];
                         }

                         // Notify UIKit that the transition has finished
                         [transitionContext completeTransition:success];
                     }];
}

這裏,大家可能注意到了一個核心的概念,那就是transitionContext,這個是實現轉場的上下文,從中我們可以獲取轉場需要的基本信息,例如fromView和toView等,下圖介紹了transitionContext和其他objects之間的關係:

這裏寫圖片描述

其實轉場需要的基本信息,都可以通過transitionContext獲取,然後實現自定的轉場動畫,動畫的實現形式不同,自然轉場的效果也就不一樣,其實,最關鍵的也就是animator的實現方式。

到此,一個簡單的自定義轉場動畫就實現了,大家可以參考一下。

總結

這裏只是簡單講解了非交互式的轉場動畫實現效果,對於交互式動畫的實現形式跟這個差不多,只是實現的animator不一樣,更詳細的講解,大家可以參考以下資料:
iOS自定義轉場動畫實戰講解
官網介紹

大家可以去我的github上下載demo:CustomTransitionTest,如果覺得有幫助還望給個star以示支持。自定義動畫中要學習的東西還很多,本文只是基於個人的理解,有不合適的地方,歡迎互相交流。

發佈了40 篇原創文章 · 獲贊 6 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章