iOS自定義轉場動畫
http://www.jianshu.com/p/45434f73019e
更新,更簡單的自定義轉場整合!
寫在前面
這兩天閒下來好好的研究了一下自定義轉場,關於這方面的文章網路上已經很多了,作為新手,我想通過這篇文章把自己這幾天的相關學習心得記錄一下,方便加深印響和以後的回顧,這是我第一寫技術文章,不好之處請諒解,通過這幾天的學習,我嘗試實現了四個效果,廢話不多說,先上效果圖:
DEMO ONE:一個彈性的present動畫,支援手勢present和dismiss
彈性pop
DEMO TWO:一個類似於KeyNote的神奇移動效果push動畫,支援手勢pop
神奇移動
DEMO THREE:一個翻頁push效果,支援手勢PUSH和POP
翻頁效果
DEMO FOUR:一個小圓點擴散present效果,支援手勢dimiss
擴散效果
動手前
大家都知道從iOS7開始,蘋果就提供了自定義轉場的API,模態推送present和dismiss、導航控制器push和pop、標籤控制器的控制器切換都可以自定義轉場了,關於過多的理論我就不太多說明了,大家可以先參照onevcat大神的這篇部落格:WWDC 2013 Session筆記 - iOS7中的ViewController切換,我想把整個自定義轉場的步驟做個總結:
-
我們需要自定義一個遵循的
<UIViewControllerAnimatedTransitioning>
協議的動畫過渡管理物件,並實現兩個必須實現的方法://返回動畫事件 - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext; //所有的過渡動畫事務都在這個方法裡面完成 - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
- 我們還需要自定義一個繼承於
UIPercentDrivenInteractiveTransition
的手勢過渡管理物件,我把它成為百分比手勢過渡管理物件,因為動畫的過程是通過百分比控制的 -
成為相應的代理,實現相應的代理方法,返回我們前兩步自定義的物件就OK了 !
模態推送需要實現如下4個代理方法,iOS8新的那個方法我暫時還沒有發現它的用處,所以暫不討論
//返回一個管理prenent動畫過渡的物件 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; //返回一個管理pop動畫過渡的物件 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed; //返回一個管理prenent手勢過渡的物件 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; //返回一個管理pop動畫過渡的物件 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;
導航控制器實現如下2個代理方法
//返回轉場動畫過渡管理物件 - (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0); //返回手勢過渡管理物件 - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
標籤控制器也有相應的兩個方法
//返回轉場動畫過渡管理物件 - (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController NS_AVAILABLE_IOS(7_0); //返回手勢過渡管理物件 - (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
-
如果看著這些常常的代理方法名頭疼的話,沒關係,先在demo中用起來吧,慢慢就習慣了,其實哪種自定義轉場都只需要這3個步驟,如果不需要手勢控制,步驟2還可以取消,現在就讓我們動手來實現效果吧
動手吧!
demo one
1、我們首先建立2個控制器,為了方便我稱做present操作的為vc1、被present的為vc2,點選一個控制器上的按鈕可以push出另一個控制器
2、 然後我們建立一個過渡動畫管理的類,遵循<UIViewControllerAnimatedTransitioning>
協議,我這裡是XWPresentOneTransition
,由於我們要同時管理present和dismiss2個動畫,你可以實現相應的兩個類分別管理兩個動畫,但是我覺得用一個類來管理就好了,看著比較舒服,邏輯也比較緊密,因為present和dismiss的動畫邏輯很類似,寫在一起,可以相互參考,所以我定義了一個列舉和兩個初始化方法:
XWPresentOneTransition.h
typedef NS_ENUM(NSUInteger, XWPresentOneTransitionType) {
XWPresentOneTransitionTypePresent = 0,//管理present動畫
XWPresentOneTransitionTypeDismiss//管理dismiss動畫
};
@interface XWPresentOneTransition : NSObject<UIViewControllerAnimatedTransitioning>
//根據定義的列舉初始化的兩個方法
+ (instancetype)transitionWithTransitionType:(XWPresentOneTransitionType)type;
- (instancetype)initWithTransitionType:(XWPresentOneTransitionType)type;
3、 然後再.m檔案裡面實現必須實現的兩個代理方法
@implementation XWPresentOneTransition
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
//為了將兩種動畫的邏輯分開,變得更加清晰,我們分開書寫邏輯,
switch (_type) {
case XWPresentOneTransitionTypePresent:
[self presentAnimation:transitionContext];
break;
case XWPresentOneTransitionTypeDismiss:
[self dismissAnimation:transitionContext];
break;
}
}
//實現present動畫邏輯程式碼
- (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
}
//實現dismiss動畫邏輯程式碼
- (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
}
4、 設定vc2的transitioningDelegate
,我就設為它自己咯,我實在vc2的init方法中設定的,並實現代理方法
- (instancetype)init
{
self = [super init];
if (self) {
self.transitioningDelegate = self;
//為什麼要設定為Custom,在最後說明.
self.modalPresentationStyle = UIModalPresentationCustom;
}
return self;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
//這裡我們初始化presentType
return [XWPresentOneTransition transitionWithTransitionType:XWPresentOneTransitionTypePresent];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
//這裡我們初始化dismissType
return [XWPresentOneTransition transitionWithTransitionType:XWPresentOneTransitionTypeDismiss];
}
5、 至此我們所有的準備工作就做好了,下面只需要專心在presentAnimation:
方法和dismissAnimation
方法中實現動畫邏輯就OK了,先看presentAnimation:
- (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{`
//通過viewControllerForKey取出轉場前後的兩個控制器,這裡toVC就是vc1、fromVC就是vc2
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
//snapshotViewAfterScreenUpdates可以對某個檢視截圖,我們採用對這個截圖做動畫代替直接對vc1做動畫,因為在手勢過渡中直接使用vc1動畫會和手勢有衝突, 如果不需要實現手勢的話,就可以不是用截圖檢視了,大家可以自行嘗試一下
UIView *tempView = [fromVC.view snapshotViewAfterScreenUpdates:NO];
tempView.frame = fromVC.view.frame;
//因為對截圖做動畫,vc1就可以隱藏了
fromVC.view.hidden = YES;
//這裡有個重要的概念containerView,如果要對檢視做轉場動畫,檢視就必須要加入containerView中才能進行,可以理解containerView管理著所有做轉場動畫的檢視
UIView *containerView = [transitionContext containerView];
//將截圖檢視和vc2的view都加入ContainerView中
[containerView addSubview:tempView];
[containerView addSubview:toVC.view];
//設定vc2的frame,因為這裡vc2present出來不是全屏,且初始的時候在底部,如果不設定frame的話預設就是整個螢幕咯,這裡containerView的frame就是整個螢幕
toVC.view.frame = CGRectMake(0, containerView.height, containerView.width, 400);
//開始動畫吧,使用產生彈簧效果的動畫API
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.55 initialSpringVelocity:1.0 / 0.55 options:0 animations:^{
//首先我們讓vc2向上移動
toVC.view.transform = CGAffineTransformMakeTranslation(0, -400);
//然後讓截圖檢視縮小一點即可
tempView.transform = CGAffineTransformMakeScale(0.85, 0.85);
} completion:^(BOOL finished) {
//使用如下程式碼標記整個轉場過程是否正常完成[transitionContext transitionWasCancelled]代表手勢是否取消了,如果取消了就傳NO表示轉場失敗,反之亦然,如果不用手勢present的話直接傳YES也是可以的,但是無論如何我們都必須標記轉場的狀態,系統才知道處理轉場後的操作,否者認為你一直還在轉場中,會出現無法互動的情況,切記!
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
//轉場失敗後的處理
if ([transitionContext transitionWasCancelled]) {
//失敗後,我們要把vc1顯示出來
fromVC.view.hidden = NO;
//然後移除截圖檢視,因為下次觸發present會重新截圖
[tempView removeFromSuperview];
}
}];
}
再看dismissAnimation
方法
- (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
//注意在dismiss的時候fromVC就是vc2了,toVC才是VC1了,注意這個關係
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//參照present動畫的邏輯,present成功後,containerView的最後一個子檢視就是截圖檢視,我們將其取出準備動畫
UIView *tempView = [transitionContext containerView].subviews[0];
//動畫吧
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//因為present的時候都是使用的transform,這裡的動畫只需要將transform恢復就可以了
fromVC.view.transform = CGAffineTransformIdentity;
tempView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
if ([transitionContext transitionWasCancelled]) {
//失敗了標記失敗
[transitionContext completeTransition:NO];
}else{
//如果成功了,我們需要標記成功,同時讓vc1顯示出來,然後移除截圖檢視,
[transitionContext completeTransition:YES];
toVC.view.hidden = NO;
[tempView removeFromSuperview];
}
}];
}
6、如果不需要手勢控制,這個轉場就算完成了,下面我們來新增手勢,首先建立一個手勢過渡管理的類,我這裡是XWInteractiveTransition
,因為無論哪一種轉場,手勢控制的實質都是一樣的,我乾脆就把這個手勢過渡管理的類封裝了一下,具體可以在.h檔案裡面檢視,在接下來的三個轉場效果中我們都可以便捷的是使用它 .m檔案說明
//通過這個方法給控制器的View新增相應的手勢
- (void)addPanGestureForViewController:(UIViewController *)viewController{
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
//將傳入的控制器儲存,因為要利用它觸發轉場操作
self.vc = viewController;
[viewController.view addGestureRecognizer:pan];
}
//關鍵的手勢過渡的過程
- (void)handleGesture:(UIPanGestureRecognizer *)panGesture{
//persent是根據panGesture的移動距離獲取的,這裡就不說明了,可具體去程式碼中檢視
switch (panGesture.state) {
case UIGestureRecognizerStateBegan:
//手勢開始的時候標記手勢狀態,並開始相應的事件,它的作用在使用這個類的時候說明
self.interation = YES;
//手勢開始是觸發對應的轉場操作,方法程式碼在後面
[self startGesture];
break;
case UIGestureRecognizerStateChanged:{
//手勢過程中,通過updateInteractiveTransition設定轉場過程進行的百分比,然後系統會根據百分比自動佈局控制元件,不用我們控制了
[self updateInteractiveTransition:persent];
break;
}
case UIGestureRecognizerStateEnded:{
//手勢完成後結束標記並且判斷移動距離是否過半,過則finishInteractiveTransition完成轉場操作,否者取消轉場操作,轉場失敗
self.interation = NO;
if (persent > 0.5) {
[self finishInteractiveTransition];
}else{
[self cancelInteractiveTransition];
}
break;
}
default:
break;
}
}
//觸發對應轉場操作的程式碼如下,根據type(type是我自定義的列舉值)我們去判斷是觸發哪種操作,對於push和present由於要傳入需要push和present的控制器,為了解耦,我用block把這個操作交個控制器去做了,讓這個手勢過渡管理者可以充分被複用
- (void)startGesture{
switch (_type) {
case XWInteractiveTransitionTypePresent:{
if (_presentConifg) {
_presentConifg();
}
}
break;
case XWInteractiveTransitionTypeDismiss:
[_vc dismissViewControllerAnimated:YES completion:nil];
break;
case XWInteractiveTransitionTypePush:{
if (_pushConifg) {
_pushConifg();
}
}
break;
case XWInteractiveTransitionTypePop:
[_vc.navigationController popViewControllerAnimated:YES];
break;
}
}
7、 手勢過渡管理者就算完畢了,這個手勢管理者可以用到其他任何的模態和導航控制器轉場中,以後都不用在寫了,現在把他用起來,在vc2和vc1中建立相應的手勢過渡管理者,並放到相應的代理方法去返回它
//建立dismiss手勢過渡管理者,present的手勢過渡要在vc1中建立,因為present的手勢是載入vc1的view上的,我選擇通過代理吧vc1中建立的手勢過渡管理者傳過來
self.interactiveDismiss = [XWInteractiveTransition interactiveTransitionWithTransitionType:XWInteractiveTransitionTypeDismiss GestureDirection:XWInteractiveTransitionGestureDirectionDown];
[self.interactiveDismiss addPanGestureForViewController:self];
[_interactivePush addPanGestureForViewController:self.navigationController];
//返回dissmiss的手勢過渡管理
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal: (id<UIViewControllerAnimatedTransitioning>)animator{
//在沒有用手勢觸發的dismiss的時候需要傳nil,否者無法點選dimiss,所以interation就是用來判斷是否是手勢觸發轉場的
return _interactiveDismiss.interation ? _interactiveDismiss : nil;
}
//返回present的手勢管理,這個手勢管理者是在vc1中建立的,我用代理傳過來的
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation: (id<UIViewControllerAnimatedTransitioning>)animator{
XWInteractiveTransition *interactivePresent = [_delegate interactiveTransitionForPresent];
return interactivePresent.interation ? interactivePresent : nil;
}
8、 終於完成了,再來看一下效果,是不是還不錯!
彈性pop
DEMO TWO
1、 建立動畫過渡管理者的程式碼就不重複說明了,我仿造demo1,利用列舉建立了一個同時管理push和pop的管理者,然後動畫的邏輯程式碼集中在doPushAnimation
和doPopAnimation
中,很多內容都在demo1中說明了,下面的註釋就比較簡單了,來看看
//Push動畫邏輯
- (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
XWMagicMoveController *fromVC = (XWMagicMoveController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
XWMagicMovePushController *toVC = (XWMagicMovePushController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//拿到當前點選的cell的imageView
XWMagicMoveCell *cell = (XWMagicMoveCell *)[fromVC.collectionView cellForItemAtIndexPath:fromVC.currentIndexPath];
UIView *containerView = [transitionContext containerView];
//snapshotViewAfterScreenUpdates 對cell的imageView截圖儲存成另一個檢視用於過渡,並將檢視轉換到當前控制器的座標
UIView *tempView = [cell.imageView snapshotViewAfterScreenUpdates:NO];
tempView.frame = [cell.imageView convertRect:cell.imageView.bounds toView: containerView];
//設定動畫前的各個控制元件的狀態
cell.imageView.hidden = YES;
toVC.view.alpha = 0;
toVC.imageView.hidden = YES;
//tempView 新增到containerView中,要保證在最前方,所以後新增
[containerView addSubview:toVC.view];
[containerView addSubview:tempView];
//開始做動畫
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.55 initialSpringVelocity:1 / 0.55 options:0 animations:^{
tempView.frame = [toVC.imageView convertRect:toVC.imageView.bounds toView:containerView];
toVC.view.alpha = 1;
} completion:^(BOOL finished) {
//tempView先隱藏不銷燬,pop的時候還會用
tempView.hidden = YES;
toVC.imageView.hidden = NO;
//如果動畫過渡取消了就標記不完成,否則才完成,這裡可以直接寫YES,如果有手勢過渡才需要判斷,必須標記,否則系統不會中動畫完成的部署,會出現無法互動之類的bug
[transitionContext completeTransition:YES];
}];
}
//Pop動畫邏輯
- (void)doPopAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
XWMagicMovePushController *fromVC = (XWMagicMovePushController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
XWMagicMoveController *toVC = (XWMagicMoveController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
XWMagicMoveCell *cell = (XWMagicMoveCell *)[toVC.collectionView cellForItemAtIndexPath:toVC.currentIndexPath];
UIView *containerView = [transitionContext containerView];
//這裡的lastView就是push時候初始化的那個tempView
UIView *tempView = containerView.subviews.lastObject;
//設定初始狀態
cell.imageView.hidden = YES;
fromVC.imageView.hidden = YES;
tempView.hidden = NO;
[containerView insertSubview:toVC.view atIndex:0];
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.55 initialSpringVelocity:1 / 0.55 options:0 animations:^{
tempView.frame = [cell.imageView convertRect:cell.imageView.bounds toView:containerView];
fromVC.view.alpha = 0;
} completion:^(BOOL finished) {
//由於加入了手勢必須判斷
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
if ([transitionContext transitionWasCancelled]) {//手勢取消了,原來隱藏的imageView要顯示出來
//失敗了隱藏tempView,顯示fromVC.imageView
tempView.hidden = YES;
fromVC.imageView.hidden = NO;
}else{//手勢成功,cell的imageView也要顯示出來
//成功了移除tempView,下一次pop的時候又要建立,然後顯示cell的imageView
cell.imageView.hidden = NO;
[tempView removeFromSuperview];
}
}];
}
2、 然後將這個動畫過渡管理者和demo1中建立的手勢過渡管理者分別放到正確的代理方法中,用起來就可以了
神奇移動
DEMO THREE
1、 直接看看doPushAnimation
和doPopAnimation
的動畫邏輯,這次使用了CAGradientLayer給動畫的過程增加了陰影
//Push動畫邏輯
- (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//還是使用截圖大法來完成動畫,不然還是會有奇妙的bug;
UIView *tempView = [fromVC.view snapshotViewAfterScreenUpdates:NO];
tempView.frame = fromVC.view.frame;
UIView *containerView = [transitionContext containerView];
//將將要動畫的檢視加入containerView
[containerView addSubview:toVC.view];
[containerView addSubview:tempView];
fromVC.view.hidden = YES;
[containerView insertSubview:toVC.view atIndex:0];
//設定AnchorPoint,並增加3D透視效果
[tempView setAnchorPointTo:CGPointMake(0, 0.5)];
CATransform3D transfrom3d = CATransform3DIdentity;
transfrom3d.m34 = -0.002;
containerView.layer.sublayerTransform = transfrom3d;
//增加陰影
CAGradientLayer *fromGradient = [CAGradientLayer layer];
fromGradient.frame = fromVC.view.bounds;
fromGradient.colors = @[(id)[UIColor blackColor].CGColor,
(id)[UIColor blackColor].CGColor];
fromGradient.startPoint = CGPointMake(0.0, 0.5);
fromGradient.endPoint = CGPointMake(0.8, 0.5);
UIView *fromShadow = [[UIView alloc]initWithFrame:fromVC.view.bounds];
fromShadow.backgroundColor = [UIColor clearColor];
[fromShadow.layer insertSublayer:fromGradient atIndex:1];
fromShadow.alpha = 0.0;
[tempView addSubview:fromShadow];
CAGradientLayer *toGradient = [CAGradientLayer layer];
toGradient.frame = fromVC.view.bounds;
toGradient.colors = @[(id)[UIColor blackColor].CGColor,
(id)[UIColor blackColor].CGColor];
toGradient.startPoint = CGPointMake(0.0, 0.5);
toGradient.endPoint = CGPointMake(0.8, 0.5);
UIView *toShadow = [[UIView alloc]initWithFrame:fromVC.view.bounds];
toShadow.backgroundColor = [UIColor clearColor];
[toShadow.layer insertSublayer:toGradient atIndex:1];
toShadow.alpha = 1.0;
[toVC.view addSubview:toShadow];
//動畫吧
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//翻轉截圖檢視
tempView.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0, 1, 0);
//給陰影效果動畫
fromShadow.alpha = 1.0;
toShadow.alpha = 0.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
if ([transitionContext transitionWasCancelled]) {
//失敗後記得移除截圖,下次push又會建立
[tempView removeFromSuperview];
fromVC.view.hidden = NO;
}
}];
}}
//Pop動畫邏輯
- (void)doPopAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = [transitionContext containerView];
//拿到push時候的的截圖檢視
UIView *tempView = containerView.subviews.lastObject;
[containerView addSubview:toVC.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//把截圖檢視翻轉回來
tempView.layer.transform = CATransform3DIdentity;
fromVC.view.subviews.lastObject.alpha = 1.0;
tempView.subviews.lastObject.alpha = 0.0;
} completion:^(BOOL finished) {
if ([transitionContext transitionWasCancelled]) {
[transitionContext completeTransition:NO];
}else{
[transitionContext completeTransition:YES];
[tempView removeFromSuperview];
toVC.view.hidden = NO;
}
}];}
2、 最後用上去在加上手勢就是這個樣子啦
翻頁效果
DEMO FOUR
1、 直接看看doPresentAnimation
和doDismissAnimation
的動畫邏輯,這次使用了CASharpLayer和UIBezierPath
//Present動畫邏輯
- (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//拿到控制器獲取button的frame來設定動畫的開始結束的路徑
UINavigationController *fromVC = (UINavigationController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
XWCircleSpreadController *temp = fromVC.viewControllers.lastObject;
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toVC.view];
//畫兩個圓路徑
UIBezierPath *startCycle = [UIBezierPath bezierPathWithOvalInRect:temp.buttonFrame];
//通過如下方法計算獲取在x和y方向按鈕距離邊緣的最大值,然後利用勾股定理即可算出最大半徑
CGFloat x = MAX(temp.buttonFrame.origin.x, containerView.frame.size.width - temp.buttonFrame.origin.x);
CGFloat y = MAX(temp.buttonFrame.origin.y, containerView.frame.size.height - temp.buttonFrame.origin.y);
//勾股定理計算半徑
CGFloat radius = sqrtf(pow(x, 2) + pow(y, 2));
//以按鈕中心為圓心,按鈕中心到螢幕邊緣的最大距離為半徑,得到轉場後的path
UIBezierPath *endCycle = [UIBezierPath bezierPathWithArcCenter:containerView.center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
//建立CAShapeLayer進行遮蓋
CAShapeLayer *maskLayer = [CAShapeLayer layer];
//設定layer的path保證動畫後layer不會回彈
maskLayer.path = endCycle.CGPath;
//將maskLayer作為toVC.View的遮蓋
toVC.view.layer.mask = maskLayer;
//建立路徑動畫
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.delegate = self;
//動畫是加到layer上的,所以必須為CGPath,再將CGPath橋接為OC物件
maskLayerAnimation.fromValue = (__bridge id)(startCycle.CGPath);
maskLayerAnimation.toValue = (__bridge id)((endCycle.CGPath));
maskLayerAnimation.duration = [self transitionDuration:transitionContext];
maskLayerAnimation.delegate = self;
//設定淡入淡出
maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[maskLayerAnimation setValue:transitionContext forKey:@"transitionContext"];
[maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
}
//Dismiss動畫邏輯
- (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UINavigationController *toVC = (UINavigationController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
XWCircleSpreadController *temp = toVC.viewControllers.lastObject;
UIView *containerView = [transitionContext containerView];
//畫兩個圓路徑
CGFloat radius = sqrtf(containerView.frame.size.height * containerView.frame.size.height + containerView.frame.size.width * containerView.frame.size.width) / 2;
UIBezierPath *startCycle = [UIBezierPath bezierPathWithArcCenter:containerView.center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
UIBezierPath *endCycle = [UIBezierPath bezierPathWithOvalInRect:temp.buttonFrame];
//建立CAShapeLayer進行遮蓋
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillColor = [UIColor greenColor].CGColor;
maskLayer.path = endCycle.CGPath;
fromVC.view.layer.mask = maskLayer;
//建立路徑動畫
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.delegate = self;
maskLayerAnimation.fromValue = (__bridge id)(startCycle.CGPath);
maskLayerAnimation.toValue = (__bridge id)((endCycle.CGPath));
maskLayerAnimation.duration = [self transitionDuration:transitionContext];
maskLayerAnimation.delegate = self;
maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[maskLayerAnimation setValue:transitionContext forKey:@"transitionContext"];
[maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
}
2、最後在animationDidStop的代理方法中處理到動畫的完成邏輯,處理方式都類似
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
switch (_type) {
case XWCircleSpreadTransitionTypePresent:{
id<UIViewControllerContextTransitioning> transitionContext = [anim valueForKey:@"transitionContext"];
[transitionContext completeTransition:YES];
[transitionContext viewControllerForKey:UITransitionContextToViewKey].view.layer.mask = nil;
}
break;
case XWCircleSpreadTransitionTypeDismiss:{
id<UIViewControllerContextTransitioning> transitionContext = [anim valueForKey:@"transitionContext"];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
if ([transitionContext transitionWasCancelled]) {
[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil;
}
}
break;
}
}
3、 最後用上去在加上手勢就是這個樣子啦
擴散效果
總結
1、關於:self.modalPresentationStyle = UIModalPresentationCustom;我查看了檢視層級後發現,如果使用了Custom,在present動畫完成的時候,presentingView也就是demo one中的vc1的view會從containerView中移除,只是移除,並未銷燬,此時還被持有著(dismiss後還得回來呢!),如果設定custom,那麼present完成後,它一直都在containerView中,只是在最後面,所以需不需要設定custom可以看動畫完成後的情況,是否還需要看見presentingViewController,但是記住如果沒有設定custom,在disMiss的動畫邏輯中,要把它加回containerView中,不然就不在咯~!
2、感覺寫了好多東西,其實只要弄懂了轉場的邏輯,其實就只需要寫動畫的邏輯就行了,其他東西都是固定的,而且蘋果提供的這種控制轉場的方式可充分解耦,除了寫的手勢過渡管理可以拿到任何地方使用,所有的動畫過渡管理者都可以很輕鬆的複用到其他轉場中,都不用分是何種轉場,demo沒有寫標籤控制器的轉場,實現方法也是完全類似的,大家可以嘗試一下,四個demo的github地址:自定義轉場動畫demo
文/wazrx(簡書作者)
原文連結:http://www.jianshu.com/p/45434f73019e
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。