iOS CATransition 自定義轉(zhuǎn)場動畫

CATransition

CATransition 是CAAnimation的子類(如下圖所示),用于控制器和控制器之間的轉(zhuǎn)場動畫。能夠來自定義系統(tǒng)的push和present。實現(xiàn)頁面間的動畫效果。

CAAnimation 關(guān)系圖

轉(zhuǎn)場動畫就是從一個場景以動畫的形式過渡到另一個場景。轉(zhuǎn)場動畫的使用一般分為以下幾個步驟:

  • 創(chuàng)建轉(zhuǎn)場動畫
  • 設(shè)置轉(zhuǎn)場類型、子類型(可選)及其他屬性畫
  • 設(shè)置轉(zhuǎn)場后的新視圖并添加動畫到圖層

ios的畫面切換的動畫效果的API主要通過調(diào)用系統(tǒng)已定義的動畫效果實現(xiàn),這些效果已基本囊括開發(fā)的需求,如果需要更加復(fù)雜的效果,可以考慮CATransition來實現(xiàn)。

以下是基本的四種效果

kCATransitionPush 推入效果
kCATransitionMoveIn 移入效果
kCATransitionReveal 截開效果
kCATransitionFade 漸入漸出效果

以下API效果可以安全使用
cube 方塊
suckEffect 三角
rippleEffect 水波抖動
pageCurl 上翻頁
pageUnCurl 下翻頁
oglFlip 上下翻轉(zhuǎn)
cameraIrisHollowOpen 鏡頭快門開
cameraIrisHollowClose 鏡頭快門開

以下API效果請慎用

spewEffect 新版面在屏幕下方中間位置被釋放出來覆蓋舊版面.
genieEffect 舊版面在屏幕左下方或右下方被吸走, 顯示出下面的新版面
unGenieEffect 新版面在屏幕左下方或右下方被釋放出來覆蓋舊版面.
twist 版面以水平方向像龍卷風(fēng)式轉(zhuǎn)出來.
tubey 版面垂直附有彈性的轉(zhuǎn)出來.
swirl 舊版面360度旋轉(zhuǎn)并淡出, 顯示出新版面.
charminUltra 舊版面淡出并顯示新版面.
zoomyIn 新版面由小放大走到前面, 舊版面放大由前面消失.
zoomyOut 新版面屏幕外面縮放出現(xiàn), 舊版面縮小消失.
oglApplicationSuspend 像按”home” 按鈕的效果.

// 使用場景 1


1.初始化
CATransition  *transition = [CATransition animation];
2.設(shè)置動畫時長,設(shè)置代理人
transition.duration = 1.0f;
transition.delegate = self;
3.設(shè)置切換速度效果
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
枚舉值:
kCAMediaTimingFunctionLinear
kCAMediaTimingFunctionEaseIn
kCAMediaTimingFunctionEaseOut
kCAMediaTimingFunctionEaseInEaseOut
kCAMediaTimingFunctionDefault
4.動畫切換風(fēng)格
transition.type = kCATransitionFade;
枚舉值:
kCATransitionFade = 1,     // 淡入淡出
kCATransitionPush,         // 推進效果
kCATransitionReveal,       // 揭開效果     
kCATransitionMoveIn,       // 慢慢進入并覆蓋效果
5.動畫切換方向
transition.subtype = kCATransitionFromTop;//頂部
枚舉值:
kCATransitionFromRight//右側(cè)
kCATransitionFromLeft//左側(cè)
kCATransitionFromTop//頂部
kCATransitionFromBottom//底部
6.進行跳轉(zhuǎn)
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:"你要跳轉(zhuǎn)的頁面" animated:NO];
跳轉(zhuǎn)動畫一定設(shè)置為NO,不然會兩個效果疊加

// 使用場景2

+ (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

CATransition-Basic -代碼傳送門

下面通過自定義push 和 present 兩個demo來分析下轉(zhuǎn)場動畫的實現(xiàn)原理:

自定義 Push

效果圖

首先要完場自定義Push 我們需要創(chuàng)建一個轉(zhuǎn)場動畫對象,繼承于NSObject (導(dǎo)入<UIKit/UIKit.h>框架)并遵守UIViewControllerAnimatedTransitioning協(xié)議, 這里叫做MagicMoveTransition。

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface MagicMoveTransition : NSObject<UIViewControllerAnimatedTransitioning>

@end

我們需要實現(xiàn)UIViewControllerAnimatedTransitioning協(xié)議里面的代理方法來實現(xiàn)轉(zhuǎn)場動畫效果。

  • 該方法返回動畫執(zhí)行時間
  • 該方法用來實現(xiàn)兩個VC間的動畫布局

transitionContext 該參數(shù) 可以讓我們?nèi)ピL問一些實現(xiàn)對象所必須的對象

UIViewControllerContextTransitioning

擴展 UIViewControllerContextTransitioning

1.  - (UIView *)containerView;
   // 轉(zhuǎn)場動畫的容器 -> 添加兩個控制器 視圖內(nèi)容 (注意添加的前后順序)
   
2.  - (UIViewController *)viewControllerForKey:(NSString *)key;
   // 通過該方法, Key值 拿到過渡的兩個VC
   
3.  - (CGRect)initialFrameForViewController:(UIViewController *)vc;
    - (CGRect)finalFrameForViewController:(UIViewController *)vc;
   // 通過這個方法 能夠獲得前后兩個 ViewController 的frame,用來布局視圖對象空間位置

現(xiàn)在我們可以通過 transitionContext開始做轉(zhuǎn)場動畫

    // 起始VC
    ViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    // 目的VC
    DeatailViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    // 轉(zhuǎn)場視圖容器
    UIView *containerView = [transitionContext containerView];

然后我們需要獲取CollectionViewCell 上面的視圖ImageView 進行做動畫

擴展 iOS , UIView 中坐標的轉(zhuǎn)換

 - (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;
    // 將像素Point 由Point所在控制器轉(zhuǎn)換到目標控制器視圖View中, 返回在目標視圖中的像素值。
 
 - (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view;
    // 由上面一條相反,獲取目標View的像素Point返回到當前控制器View中
    
 - (CGRect)convertRect:(CGRect)rect toView:(UIView *)view;
    // 將坐標frame的rect值,由當前所在的目標視圖View中, 返回在當前視圖中的rect
    
 - (CGRect)convertRect:(CGRect)rect fromView:(UIView *)view;
    // 將坐標frame的rect值,由當前所在的目標視圖View中, 返回在當前視圖中的rect

我們在這里通過獲取我們點擊的Cell,獲取上面的ImageView

- (nullable NSArray<NSIndexPath *> *)indexPathsForSelectedItems; // returns nil or an array of selected index paths

- (nullable UICollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath;

具體代碼如下

    NSIndexPath *indexPath = [[fromVC.collectionView indexPathsForSelectedItems] firstObject];
    fromVC.indexPath = indexPath; // 記錄indexPath 返回時使用
    CustomCollectionViewCell *cell = (CustomCollectionViewCell *)[fromVC.collectionView cellForItemAtIndexPath:indexPath];
    // 對Cell上面的圖片 做截圖 來實現(xiàn)過渡動畫視圖
    UIView *screenShot = [cell.pic snapshotViewAfterScreenUpdates:NO];
    screenShot.backgroundColor = [UIColor clearColor];
    screenShot.frame = fromVC.finiRect = [containerView convertRect:cell.pic.frame fromView:cell.pic.superview];
    // fromVC.finiRect 返回時獲取cell上的坐標
    cell.pic.hidden = YES;
    // 在進入下一個界面時,隱藏當前Cell上面的imageView 

然后我們 來 設(shè)置第二個控制器的位置和透明度

    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    toVC.view.alpha = 0.5;
    toVC.secondPic.hidden = YES;
    // 把動畫前后的兩個ViewController加到容器控制器中
    [containerView addSubview:toVC.view];
    [containerView addSubview:screenShot];
        // 注意添加順序

現(xiàn)在開始做動畫,可以根據(jù)你的項目需求來獲取,控制器內(nèi)的控件,來完成彼此間的動畫交互。

  • 這里 :[self transitionDuration:transitionContext]會返回轉(zhuǎn)場動畫所進行時間。
  • 執(zhí)行該語句來告訴系統(tǒng)轉(zhuǎn)場動畫結(jié)束, 這里使用!transitionContext.transitionWasCancelled,是為了后面做手勢交互,避免手勢取消時,造成卡頓現(xiàn)象。
    [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
    }];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        // 調(diào)用自動布局
        [containerView layoutIfNeeded];
        fromVC.view.alpha = 1.0;
        // 布局坐標
       // 獲取到下一個VC中的secondPic.frame,使Cell上面的截圖,移動到第二VC視圖中, 動畫結(jié)束后, 移除該截圖。視覺上會產(chǎn)生一種過渡效果。
        screenShot.frame = [containerView convertRect:toVC.secondPic.frame toView:toVC.secondPic.superview];
    } completion:^(BOOL finished) {
        toVC.secondPic.hidden = NO;
        cell.pic.hidden = NO;
        // 動畫截圖移除View
        [screenShot removeFromSuperview];
        toVC.view.alpha = 1;
        // 動畫結(jié)束
        // 一定不要忘記告訴系統(tǒng)動畫結(jié)束
        // 執(zhí)行
        [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];

現(xiàn)在我們就可以來實現(xiàn)來使用自定義的轉(zhuǎn)場動畫對象

這里的轉(zhuǎn)場動畫屬于自定義push, 由導(dǎo)航欄來控制,屬于導(dǎo)航控制器來負責(zé)轉(zhuǎn)場。我們讓當前控制器來作為 UINavigationControllerDelegate代理對象, 并實現(xiàn)協(xié)議方法。

在視圖已經(jīng)出現(xiàn)的時候來指定代理對象為自身

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    self.navigationController.delegate = self;
}
#pragma mark - 返回我們寫好的轉(zhuǎn)場對象
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                            animationControllerForOperation:(UINavigationControllerOperation)operation
                                                         fromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC
{
    if ([toVC isKindOfClass:[DeatailViewController class]]) {
        MagicMoveTransition *transition = [[MagicMoveTransition alloc] init];
        return transition;
    }
    return nil;
}

到這里我們push轉(zhuǎn)場動畫就已經(jīng)實現(xiàn)了, pop回來的動畫我們可以類比push來實現(xiàn)。

#import "MagicMoveBackTransition.h"
#import "ViewController.h"
#import "DeatailViewController.h"
#import "CustomCollectionViewCell.h"
@implementation MagicMoveBackTransition

#pragma mark - 返回動畫時間

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return 0.6f;
}
#pragma mark - 兩個VC過渡動畫
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    // 目的VC
    ViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    // 起始VC
    DeatailViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    // 轉(zhuǎn)場視圖容器
    UIView *containerView = [transitionContext containerView];
      UIView *screenShot = [fromVC.secondPic snapshotViewAfterScreenUpdates:NO];
    screenShot.backgroundColor = [UIColor clearColor];
    screenShot.frame = [containerView convertRect:fromVC.secondPic.frame fromView:fromVC.secondPic.superview];
    fromVC.secondPic.hidden = YES;
    
    // 初始化第二個Vc
    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    
    CustomCollectionViewCell *cell = (CustomCollectionViewCell *)[toVC.collectionView cellForItemAtIndexPath:toVC.indexPath];
    cell.pic.hidden = YES;
    [containerView insertSubview:toVC.view belowSubview:fromVC.view];
    [containerView addSubview:screenShot];
    // 發(fā)生動畫
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        
        fromVC.view.alpha = 0;
        screenShot.frame = toVC.finiRect;
        
        
    } completion:^(BOOL finished) {
        
        [screenShot removeFromSuperview];
        fromVC.secondPic.hidden = NO;
        cell.pic.hidden = NO;
        fromVC.view.alpha = 1;
        // 結(jié)束
        [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
    }];
    }
@end

另外我們可以通過添加手勢,來能實現(xiàn)系統(tǒng)手勢驅(qū)動;UIPercentDrivenInteractiveTransition 能夠幫助我們完成視圖切換的過度動畫

在 DeatailViewController pop回來時添加屏幕邊緣手勢

    // 添加屏幕邊緣手勢
    UIScreenEdgePanGestureRecognizer *edgePagGesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgeAction:)];
    edgePagGesture.edges = UIRectEdgeLeft; // 設(shè)置什么便捷滑入
    [self.view addGestureRecognizer:edgePagGesture];

// 實現(xiàn)手勢方法

iOS7 新加入一個類對象 UIPercentDrivenInteractiveTransition ;這個對象會根據(jù)我們的手勢,來決定我們界面跳轉(zhuǎn)的自定義過渡效果,我們在手勢action方法中,對手勢驅(qū)動狀態(tài)進行判斷,來決定是否過渡動畫。

- (void)edgeAction:(UIScreenEdgePanGestureRecognizer *)sender
{
    // 計算手指滑動的距離
    
    // 計算手勢驅(qū)動占屏幕的百分比
    CGFloat distance = [sender translationInView:sender.view].x / self.view.bounds.size.width;
    distance = MIN(1.0, MAX(0.0, distance));
    // 限制百分比 0 - 1 之間
    
    if (sender.state == UIGestureRecognizerStateBegan) {
        self.percentDrivenTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
        [self.navigationController popViewControllerAnimated:YES];
    }
    else if (sender.state == UIGestureRecognizerStateChanged)
    {
        // 手勢在慢慢劃入 // 把手勢的進度告訴 UIPercentDrivenInteractiveTransition
        [self.percentDrivenTransition updateInteractiveTransition:distance];
    }
    else if (sender.state == UIGestureRecognizerStateEnded || sender.state == UIGestureRecognizerStateCancelled)
    {
// 判斷當手勢到達一定范圍內(nèi) 
        if (distance > 0.5) {
            [self.percentDrivenTransition finishInteractiveTransition];
        }
        else
        {
            [self.percentDrivenTransition cancelInteractiveTransition];
        }
        self.percentDrivenTransition = nil;
    }
}

// 執(zhí)行手勢驅(qū)動代理方法

- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
    if ([animationController isKindOfClass:[MagicMoveBackTransition class]]) {
        return self.percentDrivenTransition;
    }
    else
    {
        return nil;
    }
}


到這里我們就完成了自定義push
代碼傳送門

自定義 Present

效果圖
  • 自定義present和自定push相似,自定義present負責(zé)轉(zhuǎn)場的是導(dǎo)航控制器,自定present負責(zé)的轉(zhuǎn)場的是視圖控制器本身.
  • 視圖處理的邏輯一樣 ;分別實現(xiàn)基于 UIViewControllerAnimatedTransitioning的present 和dismiss 詳細參考自定義push,下面講下區(qū)別的地方.

// 在第一個present 推出的視圖那里,我們需要讓該控制器遵守 <UIViewControllerTransitioningDelegate>

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    self.transitioningDelegate = self;
}

需要注意的是目標VC也需要遵守該協(xié)議;這里以prepareForSegue為例

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    SecondViewController *secondVc = (SecondViewController *)segue.destinationViewController;
    
    // 注意一定要把目標值 也作為當前 UIViewControllerTransitioningDelegate的代理人
    secondVc.transitioningDelegate = self;
    [super prepareForSegue:segue sender:sender];
}

同時目標VC 遵守UIViewControllerTransitioningDelegate

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    self.transitioningDelegate = self;
    
    // 添加屏幕邊緣手勢
    UIScreenEdgePanGestureRecognizer *edgePagGesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgeAction:)];
    edgePagGesture.edges = UIRectEdgeLeft; // 設(shè)置什么便捷滑入
    [self.view addGestureRecognizer:edgePagGesture];
    
}

詳細的代碼這里就不貼出來了.
詳情代碼
github

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容