https://www.jianshu.com/p/39c051cfe7ddios
CATransition
CATransition 是CAAnimation的子類(以下圖所示),用於控制器和控制器之間的轉場動畫。可以來自定義系統的push和present。實現頁面間的動畫效果。git
轉場動畫就是從一個場景以動畫的形式過渡到另外一個場景。轉場動畫的使用通常分爲如下幾個步驟:
- 建立轉場動畫
- 設置轉場類型、子類型(可選)及其餘屬性畫
- 設置轉場後的新視圖並添加動畫到圖層
ios的畫面切換的動畫效果的API主要經過調用系統已定義的動畫效果實現,這些效果已基本囊括開發的需求,若是須要更加複雜的效果,能夠考慮CATransition來實現。github
如下是基本的四種效果安全
kCATransitionPush 推入效果
kCATransitionMoveIn 移入效果
kCATransitionReveal 截開效果
kCATransitionFade 漸入漸出效果框架
如下API效果能夠安全使用
cube 方塊
suckEffect 三角
rippleEffect 水波抖動
pageCurl 上翻頁
pageUnCurl 下翻頁
oglFlip 上下翻轉
cameraIrisHollowOpen 鏡頭快門開
cameraIrisHollowClose 鏡頭快門開佈局
如下API效果請慎用動畫
spewEffect 新版面在屏幕下方中間位置被釋放出來覆蓋舊版面.
genieEffect 舊版面在屏幕左下方或右下方被吸走, 顯示出下面的新版面
unGenieEffect 新版面在屏幕左下方或右下方被釋放出來覆蓋舊版面.
twist 版面以水平方向像龍捲風式轉出來.
tubey 版面垂直附有彈性的轉出來.
swirl 舊版面360度旋轉並淡出, 顯示出新版面.
charminUltra 舊版面淡出並顯示新版面.
zoomyIn 新版面由小放大走到前面, 舊版面放大由前面消失.
zoomyOut 新版面屏幕外面縮放出現, 舊版面縮小消失.
oglApplicationSuspend 像按」home」 按鈕的效果.url
// 使用場景 1spa
1.初始化代理
CATransition *transition = [CATransition animation];
2.設置動畫時長,設置代理人
transition.duration = 1.0f;
transition.delegate = self;
3.設置切換速度效果
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
枚舉值:
kCAMediaTimingFunctionLinear
kCAMediaTimingFunctionEaseIn
kCAMediaTimingFunctionEaseOut
kCAMediaTimingFunctionEaseInEaseOut
kCAMediaTimingFunctionDefault
4.動畫切換風格
transition.type = kCATransitionFade;
枚舉值:
kCATransitionFade = 1, // 淡入淡出
kCATransitionPush, // 推動效果
kCATransitionReveal, // 揭開效果
kCATransitionMoveIn, // 慢慢進入並覆蓋效果
5.動畫切換方向
transition.subtype = kCATransitionFromTop;//頂部
枚舉值:
kCATransitionFromRight//右側
kCATransitionFromLeft//左側
kCATransitionFromTop//頂部
kCATransitionFromBottom//底部
6.進行跳轉
[self.navigationController.view.layer addAnimation:transition forKey:nil];
[self.navigationController pushViewController:"你要跳轉的頁面" animated:NO];
跳轉動畫必定設置爲NO,否則會兩個效果疊加
+ (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
下面經過自定義push 和 present 兩個demo來分析下轉場動畫的實現原理:
自定義 Push
首先要完場自定義Push 咱們須要建立一個轉場動畫對象,繼承於NSObject (導入<UIKit/UIKit.h>框架)並遵照UIViewControllerAnimatedTransitioning協議, 這裏叫作MagicMoveTransition。
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface MagicMoveTransition : NSObject<UIViewControllerAnimatedTransitioning>
@end
咱們須要實現UIViewControllerAnimatedTransitioning協議裏面的代理方法來實現轉場動畫效果。
該方法返回動畫執行時間
、、、、、
該方法用來實現兩個VC間的動畫布局
、、、、、
transitionContext 該參數 可讓咱們去訪問一些實現對象所必須的對象
擴展 UIViewControllerContextTransitioning
1. - (UIView *)containerView;
// 轉場動畫的容器 -> 添加兩個控制器 視圖內容 (注意添加的先後順序)
2. - (UIViewController *)viewControllerForKey:(NSString *)key;
// 經過該方法, Key值 拿到過渡的兩個VC
3. - (CGRect)initialFrameForViewController:(UIViewController *)vc;
- (CGRect)finalFrameForViewController:(UIViewController *)vc;
// 經過這個方法 可以得到先後兩個 ViewController 的frame,用來佈局視圖對象空間位置
如今咱們能夠經過 transitionContext開始作轉場動畫
// 起始VC
ViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
// 目的VC
DeatailViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 轉場視圖容器
UIView *containerView = [transitionContext containerView];
而後咱們須要獲取CollectionViewCell 上面的視圖ImageView 進行作動畫
擴展 iOS , UIView 中座標的轉換
- (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)view;
// 將像素Point 由Point所在控制器轉換到目標控制器視圖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上面的圖片 作截圖 來實現過渡動畫視圖
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
而後咱們 來 設置第二個控制器的位置和透明度
toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
toVC.view.alpha = 0.5;
toVC.secondPic.hidden = YES;
// 把動畫先後的兩個ViewController加到容器控制器中
[containerView addSubview:toVC.view];
[containerView addSubview:screenShot];
// 注意添加順序
如今開始作動畫,能夠根據你的項目需求來獲取,控制器內的控件,來完成彼此間的動畫交互。
這裏 :[self transitionDuration:transitionContext]會返回轉場動畫所進行時間。
執行該語句來告訴系統轉場動畫結束, 這裏使用!transitionContext.transitionWasCancelled,是爲了後面作手勢交互,避免手勢取消時,形成卡頓現象。
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
// 調用自動佈局
[containerView layoutIfNeeded];
fromVC.view.alpha = 1.0;
// 佈局座標
// 獲取到下一個VC中的secondPic.frame,使Cell上面的截圖,移動到第二VC視圖中, 動畫結束後, 移除該截圖。視覺上會產生一種過渡效果。
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;
// 動畫結束
// 必定不要忘記告訴系統動畫結束
// 執行
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
如今咱們就能夠來實現來使用自定義的轉場動畫對象
這裏的轉場動畫屬於自定義push, 由導航欄來控制,屬於導航控制器來負責轉場。咱們讓當前控制器來做爲 UINavigationControllerDelegate代理對象, 並實現協議方法。
在視圖已經出現的時候來指定代理對象爲自身
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.navigationController.delegate = self;
}
#pragma mark - 返回咱們寫好的轉場對象
- (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轉場動畫就已經實現了, pop回來的動畫咱們能夠類比push來實現。
#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];
// 轉場視圖容器
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];
// 發生動畫
[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;
// 結束
[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
}];
}
@end
另外咱們能夠經過添加手勢,來能實現系統手勢驅動;UIPercentDrivenInteractiveTransition 可以幫助咱們完成視圖切換的過分動畫
在 DeatailViewController pop回來時添加屏幕邊緣手勢
// 添加屏幕邊緣手勢
UIScreenEdgePanGestureRecognizer *edgePagGesture = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgeAction:)];
edgePagGesture.edges = UIRectEdgeLeft; // 設置什麼便捷滑入
[self.view addGestureRecognizer:edgePagGesture];
// 實現手勢方法
iOS7 新加入一個類對象 UIPercentDrivenInteractiveTransition ;這個對象會根據咱們的手勢,來決定咱們界面跳轉的自定義過渡效果,咱們在手勢action方法中,對手勢驅動狀態進行判斷,來決定是否過渡動畫。
- (void)edgeAction:(UIScreenEdgePanGestureRecognizer *)sender
{
// 計算手指滑動的距離
// 計算手勢驅動佔屏幕的百分比
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)
{
// 判斷當手勢到達必定範圍內
if (distance > 0.5) {
[self.percentDrivenTransition finishInteractiveTransition];
}
else
{
[self.percentDrivenTransition cancelInteractiveTransition];
}
self.percentDrivenTransition = nil;
}
}
// 執行手勢驅動代理方法
- (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負責轉場的是導航控制器,自定present負責的轉場的是視圖控制器自己.
- 視圖處理的邏輯同樣 ;分別實現基於 UIViewControllerAnimatedTransitioning的present 和dismiss 詳細參考自定義push,下面講下區別的地方.
// 在第一個present 推出的視圖那裏,咱們須要讓該控制器遵照 <UIViewControllerTransitioningDelegate>
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; self.transitioningDelegate = self; }
須要注意的是目標VC也須要遵照該協議;這裏以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; // 設置什麼便捷滑入 [self.view addGestureRecognizer:edgePagGesture]; }
詳細的代碼這裏就不貼出來了.
詳情代碼
github