什麼是轉場動畫:html
轉場動畫說的直接點就是你常見的界面跳轉的時候看到的動畫效果,咱們比較常見的就是控制器之間的Push和Pop,還有Present和Dismiss的時候設置一下系統給咱們的modalTransitionStyle,以及經過手勢的左滑或者是右滑的轉場等等,這些就是咱們比較常見的,固然很大部分APP轉場的方式也是咱們上面說的常見的。我本身的建議和理解,轉場動畫能幫你加深理解、總結你對動畫的學習,但不要輕易在你的項目中大量的去嘗試,仍是以爲動畫用的好就有點睛之筆的感受,但如果大量的使用,很容易給人形成審美和視覺疲勞。固然這個可能就是對大家設計或者是產品功力的考驗了。要他們真的作出了點睛的動畫也是但願咱們搞的定。ios
咱們要說的確定就不是咱們常見的轉場了,在那些特殊的轉場動畫面前咱們應該怎麼作。針對這問題,咱們分開一步一步的解析,不想讓篇幅太長了,咱們在這裏會分紅兩篇,第一篇主要說理論的地方和穿插一些小案例,第二篇我會把本身最近學習過得一下案例最好全都分享給你們,一塊兒學習。git
一:Transition(n. 過渡;轉變;[分子生物] 轉換;變調)github
這個單詞估計就是咱們轉場的基礎了,留給英文可能不是那麼6的你我他。在下面你確定會大量的看到它,對於這個Transition(轉場)過程當中視圖控制器和其對應的視圖在結構上的變化我在巧神的博客中看到這張圖,說實話,不太理解這張圖表達了的是什麼,把這張圖給你們分享出來,你要理解的話能夠留言你們討論一下,接下來先說說在理解轉場以前咱們須要理解的幾個概念:app
*** 官方支持如下幾種方式的自定義轉場:函數
一、咱們最多見的在 UINavigationController 中 push 和 pop;佈局
二、也是比較常見的在 UITabBarController 中切換 Tab;學習
三、Modal 轉場:presentation 和 dismissal,俗稱視圖控制器的模態顯示和消失,僅限於modalPresentationStyle屬性爲 UIModalPresentationFullScreen 或 UIModalPresentationCustom 這兩種模式,這裏要區分上面說的modalTransitionStyle,下面會區分這兩個屬性。測試
四、UICollectionViewController 的佈局轉場:UICollectionViewController 與 UINavigationController 結合的轉場方式;動畫
*** 區分modalTransitionStyle和modalPresentationStyle,首先它們倆都是UIViewController的屬性:
一、先說說 modalTransitionStyle,這個是控制器跳轉時系統給的幾個動畫風格,這個在iPhone上用的比較多:
typedef NS_ENUM(NSInteger, UIModalTransitionStyle) { // 默認的從下到上 UIModalTransitionStyleCoverVertical = 0, // 翻轉 UIModalTransitionStyleFlipHorizontal __TVOS_PROHIBITED, // 漸顯 UIModalTransitionStyleCrossDissolve, // 相似你翻書時候的效果 UIModalTransitionStylePartialCurl NS_ENUM_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED, };
二、再說說modalPresentationStyle,這個是彈出時控制器的風格,modalPresentationStyle的分割在iPad上面通通有效,但在iPhone和iPod touch上面系統始終已UIModalPresentationFullScreen模式顯示presentedController,關於modalPresentationStyle在下面也經過註釋說一下:
typedef NS_ENUM(NSInteger, UIModalPresentationStyle) { //presented控制器充滿全屏,若是彈出VC的wantsFullScreenLayout設置爲YES的,則會填充到狀態欄下邊,不然不會填充到狀態欄之下.iPhone默認是這個 UIModalPresentationFullScreen = 0, //presented控制器的高度和當前屏幕高度相同,寬度和豎屏模式下屏幕寬度相同,剩餘未覆蓋區域將會變暗並阻止用戶點擊,這種彈出模式下,豎屏時跟UIModalPresentationFullScreen的效果同樣,橫屏時候兩邊則會留下變暗的區域 UIModalPresentationPageSheet NS_ENUM_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED, //presented控制器的高度和寬度均會小於屏幕尺寸,presented VC居中顯示,四周留下變暗區域。 UIModalPresentationFormSheet NS_ENUM_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED, //presented控制器的彈出方式和presenting VC的父VC的方式相同。 UIModalPresentationCurrentContext NS_ENUM_AVAILABLE_IOS(3_2), //自定義 UIModalPresentationCustom NS_ENUM_AVAILABLE_IOS(7_0), UIModalPresentationOverFullScreen NS_ENUM_AVAILABLE_IOS(8_0), UIModalPresentationOverCurrentContext NS_ENUM_AVAILABLE_IOS(8_0), // http://www.15yan.com/story/jlkJnPmVGzc/ 在iPad上彈出控制器 UIModalPresentationPopover NS_ENUM_AVAILABLE_IOS(8_0) __TVOS_PROHIBITED, // None UIModalPresentationNone NS_ENUM_AVAILABLE_IOS(7_0) = -1, };
*** Presented和Presenting fromView和toView
這個藉助於我看博客的時候看到的同行總結的比較好的一句話和圖示說明來講一下:
Presented和Presenting是一組相對的概念,它不受present或dismiss的影響,若是是從A視圖控制器present到B,那麼A老是B的presentingViewController
,B老是A的presentedViewController
。
順便藉助於這張圖示說明,咱們還能夠理解一下fromView和toView這個兩個概念:
fromView表示當前視圖toView表示要跳轉到的視圖。若是是從A視圖控制器present到B,則A是fromView,B是toView。從B視圖控制器dismiss到A時,B變成了fromView,A是toView。在後面在參考博客中我都會把這些博客連接總結髮出來。
二:轉場的幾個關鍵點
轉場最關鍵的地方就是幾個轉場協議,咱們分開一個一個的說這幾個轉場的協議,在說這幾個協議的過程當中穿插一些簡單的轉場動畫的案列,這些例子最後都會上傳到git上去。
一、 轉場協議:UIViewControllerTransitioningDelegate
這個協議裏面有五個方法,先看看這五個方法,而後把這幾個方法逐個解析一下:
先給你們再普及一個單詞!哈哈...最後兩個方法有這個interaction( 相互做用;[數] 交互做用),你就理解它就是交互
** 下面是這幾個方法的代碼的註釋,它的一些注意的地方以及一些解釋在下面代碼的註釋中有,看了下面的方法,咱們也就大概掌握了這個協議:
#pragma mark - UIViewControllerTransitioningDelegate /* 不論是 present 仍是dismiss 要是調用interactionControllerForPresentation 或者是 interactionControllerForDismissal 返回值是nil,就會走下面animationControllerForPresentedController和animationControllerForDismissedController方法 要是否是nil,就不會走下面這兩個方法了, 在咱們這裏也就是用手勢測試的時候是不會走的,點擊present或 者是dismiss會走 */ // 這個方法返回一個遵照 <UIViewControllerAnimatedTransitioning> 協議的對象 // 其實返回的就是PresentedController控制器的動畫 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{ //ForithmAnimation這個類能夠在Demo中去看,下面咱們也會說 return [ForithmAnimation new]; } // 這個方法和上面的解釋是相似的,只不過這裏的控制器就是DismissedController - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{ //ForithmAnimation 遵照 UIViewControllerAnimatedTransitioning 協議 return [ForithmAnimation new]; } // UIKit還會調用代理的interactionControllerForPresentation:方法來獲取交互式控制器,若是獲得了nil則執行非交互式動畫 // 若是獲取到了不是nil的對象,那麼UIKit不會調用animator的animateTransition方法,而是調用交互式控制器的startInteractiveTransition:方法。 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator{}; // 這個方法是在dismiss的時候的時候調用,也是交互轉場執行的時候 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator{}; // 這個方法的返回值是UIPresentationController // UIPresentationController提供了四個函數來定義present和dismiss動畫開始先後的操做,這個咱們在下面再具體的詳細說 - (nullable UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(nullable UIViewController *)presenting sourceViewController:(UIViewController *)source NS_AVAILABLE_IOS(8_0){};
咱們接着我說第二點動畫協議,這兩個說完了,咱們說一個簡單的實例.
二、 動畫協議: UIViewControllerAnimatedTransitioning
仍是老辦法,咱們要用的它的話咱們得先了解它都有些什麼東西,點進去看看它裏面的方法,把方法也解釋一下:
這個協議看的出來仍是很簡單的,終於不用那麼長了是嗎?哈哈.....
這兩個方法咱們就不在代碼裏面添加註釋說明了,在這裏一句話描述一下:
a: 第一個方法是返回動畫執行的一個時間,建議設置在0.5之內吧。
b: 核心方法,轉場動畫咱們就是在這個方法裏面添加的,因此,通常講動畫的文章,轉場動畫都會在最後說說,由於它須要基本動畫做爲一個基礎。
三、 轉場環境協議 UIViewControllerContextTransitioning
不知道你有沒有注意到上面咱們說的 UIViewControllerAnimatedTransitioning 協議的第二個方法裏面,有個參數叫transitionContext 它的類型呢?它的最主要的做用就是獲取到轉場上下文,在接下來的例子中,你們注意下這個方法:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
在它裏面最開始的時候,咱們都會去獲取 fromViewController、或者是toViewController亦或是後面的fromView、toView等這些內容,還有contentView,這些就是他最主要的做用。
EXAMPLE-ONE:
下面的GIF實例分爲三個,咱們用咱們上面說的第一點個第二點要素就能完成的是第一種,逐漸顯示,第二種的話須要咱們接下來要說的第三點交互控制器協議方法面的東西,咱們就在下面第三點說完再說;
這裏是我學習這些內容的原文的博客的地址你們能夠去看看原文,原文連接Demo還有Swift版本的Demo給你們,感謝做者!
Demo的下載地址我在這裏給你們,咱們如今說的就先是第一種:逐漸出現的轉場
前面的用UICollectionView寫的那個圈圈,哈哈.....圈圈代碼在ViewController裏面,重要的其實就是每個attributes的center屬性,很簡答的就不說了,相信你們也都懂。
重點代碼咱們說說,在這裏說過的咱們在下面的代碼中就會一筆帶過不在解釋了:
// 點擊跳轉事件 -(void)presentNextControllerClicked{ // 跳轉到這個控制器ForithmToViewController,固然是繼承與UIViewController ForithmToViewController * toViewController =[[ForithmToViewController alloc]init]; toViewController.modalPresentationStyle = UIModalPresentationFullScreen; // NOTE:轉場的關鍵就是這個代理 transitioningDelegate // 指定了這個代理就須要遵照UIViewControllerTransitioningDelegate這個協議 // 協議裏面的東西點進去能夠仔細看看,咱們指定toViewController的transitioningDelegate是咱們的ForithmFromViewController,也就是 // fromViewController,這樣咱們的fromViewController就要遵照這個協議 toViewController.transitioningDelegate = self; [self presentViewController:toViewController animated:YES completion:nil]; } #pragma mark - UIViewControllerTransitioningDelegate // 這個方法返回一個遵照 <UIViewControllerAnimatedTransitioning> 協議的對象 // 其實返回的就是PresentedController控制器的動畫 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{ return [ForithmAnimation new]; } // 這個方法和上面的解釋是相似的,只不過這裏的控制器就是DismissedController - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{ //ForithmAnimation 遵照 UIViewControllerAnimatedTransitioning 協議 return [ForithmAnimation new]; }
如今這個重點就落在咱們這兒,遵照了UIViewControllerAnimatedTransitioning協議的ForithmAnimation:順便也提一下這就是轉場動畫API強大的地方,它們都是一些協議API,無論你是誰,只要遵照這些個協議就OK,便捷了許多。也利於咱們封裝,這也就是那些第三方的轉場庫你拿來就能直接用的緣由。
接着說咱們的ForithmAnimation,讓它遵照<UIViewControllerAnimatedTransitioning>,咱們看下協議的方法,你能夠看到重點全都在咱們上面說的動畫方法裏面:
// 這個方法簡單,就是轉場動畫執行的時間 - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{ return 0.35; } // This method can only be a nop if the transition is interactive and not a percentDriven interactive transition. - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{ // fromViewController UIViewController * fromViewController = [transitionContext viewControllerForKey:( UITransitionContextFromViewControllerKey)]; // toViewController UIViewController * toViewController = [transitionContext viewControllerForKey:( UITransitionContextToViewControllerKey)]; /* typedef NS_ENUM(NSInteger, UIModalTransitionStyle) { // 默認的從下到上 UIModalTransitionStyleCoverVertical = 0, // 翻轉 UIModalTransitionStyleFlipHorizontal __TVOS_PROHIBITED, // 漸顯 UIModalTransitionStyleCrossDissolve, // 相似你翻書時候的效果 UIModalTransitionStylePartialCurl NS_ENUM_AVAILABLE_IOS(3_2) __TVOS_PROHIBITED, };*/ // UIView * contentView = [transitionContext containerView]; UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey]; UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey]; fromView.frame = [transitionContext initialFrameForViewController:fromViewController]; toView.frame = [transitionContext finalFrameForViewController:toViewController]; fromView.alpha = 1.0f; toView.alpha = 0.0f; // 在present和,dismiss時,必須將toview添加到視圖層次中 [contentView addSubview:toView]; // 獲取執行時長 NSTimeInterval transitionDuration = [self transitionDuration:transitionContext]; [UIView animateWithDuration:transitionDuration animations:^{ fromView.alpha = 0.0f; toView.alpha = 1.0; } completion:^(BOOL finished) { //transitionWasCancelled 這個方法判斷轉場是否已經取消了,下面的completeTransition設置轉場完成 //動畫結束後必定要調用completeTransition方法 //經過transitionWasCancelled()方法來獲取轉場的狀態,使用completeTransition:來完成或取消轉場。 BOOL wasCancelled = [transitionContext transitionWasCancelled]; [transitionContext completeTransition:!wasCancelled]; }]; }
上面方法,一個簡單的自定義轉場咱們就完成了,明白了上面這第一點個第二點的要素,理解這個轉場相信對你也不是什麼問題,咱們接着往下說。
四、 交互控制器協議 UIViewControllerInteractiveTransitioning
說這個UIViewControllerInteractiveTransitioning你就得先知道這個UIPercentDrivenInteractiveTransition,官方的連接給你們,先看的能夠去看看,這是一個實現了UIViewControllerInteractiveTransitioning接口的類,爲咱們預先實現和提供了一系列便利的方法,能夠用一個百分比來控制交互式切換的過程。利用手勢來完成這個轉場,UIPercentDrivenInteractiveTransition爲咱們提供了很大的便利:
爲了咱們的篇幅考慮,不想一篇太長了,否則真的會沒有耐心看下去,咱們在這裏就簡單看看這個UIPercentDrivenInteractiveTransition的一些方法,它裏面的一些屬性什麼的咱們就不說了,你們能夠本身去看看:
它裏面的方法就這四個,簡單說下這四個方法:
a: 第一個方法是暫停交互
b: 第二個是更新方法,通常交互時候的進度更新就在這個方法裏面
c: 第三個是取消交互
d: 第四個的話就是設置交互完成
EXAMPLE-TWO
如今來講第二個轉場的實現:
一、這個轉場須要一個最基本的 UIScreenEdgePanGestureRecognizer 手勢,它是一個屏幕邊緣滑動手勢,這個手勢是繼承自UIPanGestureRecognizer滑動手勢的。這個是手勢說一點,就是它的 edges 屬性,你要往左邊拉動轉場的話你就須要設置這個屬性爲UIRectEdgeRight,一個很簡單的理解就是往左邊拉動你須要設置它相應右邊的滑動手勢,這樣理解就OK。
//UIScreenEdgePanGestureRecognizer:UIPanGestureRecognizer //添加屏幕邊緣滑動手勢 UIScreenEdgePanGestureRecognizer * interactiveTransitionRecognizer; interactiveTransitionRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(interactiveTransitionRecognizerAction:)]; // 響應右邊的滑動事件 interactiveTransitionRecognizer.edges = UIRectEdgeRight; [self.view addGestureRecognizer:interactiveTransitionRecognizer];
二、接下來就按照咱們說的上面的第一個轉場的理解,設置咱們 toViewController 的 transitioningDelegate 代理,接下來的是就須要咱們再這個代理中去看了,你們先彆着急去看,想想,咱們說的代理那五個方法裏面和交互有關的兩個,前面咱們說過,設置了交互就不走動畫方法了,交互哪裏你須要返回的就是一個上面咱們說的遵照UIViewControllerInteractiveTransitioning協議的類,這時候上面說的UIPercentDrivenInteractiveTransition就華麗的出場了,注意下面這個方法,固然這是Presentation,咱們的Dismissal也是你們能夠去Demo裏面看,道理是同樣的:
// UIKit還會調用代理的interactionControllerForPresentation:方法來獲取交互式控制器,若是獲得了nil則執行非交互式動畫 // 若是獲取到了不是nil的對象,那麼UIKit不會調用animator的animateTransition方法,而是調用交互式控制器的startInteractiveTransition:方法。 // interaction 交互 - (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator { // 說說這裏的SwipTransitionInteractionController 控制器, // 它是繼承 UIPercentDrivenInteractiveTransition 的,而這個UIPercentDrivenInteractiveTransition是遵照了 // UIViewControllerInteractiveTransitioning 協議的,因此這裏初始化返回這個是沒有問題的 // 是手勢操做,就返回這個交互式控制器 if (self.gestureRecognizer) return [[SwipTransitionInteractionController alloc] initWithGestureRecognizer:self.gestureRecognizer edgeForDragging:self.targetEdge]; else return nil; }
看了上面的代碼,咱們的UIPercentDrivenInteractiveTransition就算出場了,SwipTransitionInteractionController是繼承自UIPercentDrivenInteractiveTransition,你也知道UIPercentDrivenInteractiveTransition遵照了UIViewControllerInteractiveTransitioning協議,這裏你也就應該理解咱們初始化SwipTransitionInteractionController返回的意思了。
接着看咱們SwipTransitionInteractionController裏面核心的代碼:
/** 前面代理經過 interactionControllerForPresentation 方法獲取交互控制器的時候,手勢返回的就是SwipTransitionInteractionController,這個時候就會調用這個方法 interactive 交互 */ -(void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext{ [super startInteractiveTransition:transitionContext]; self.transitionContext = transitionContext; } // 手勢觸發該方法 -(void)gestureRecognizeDidUpdate:(UIScreenEdgePanGestureRecognizer *)gestureRecognizer{ switch (gestureRecognizer.state){ case UIGestureRecognizerStateBegan: break; case UIGestureRecognizerStateChanged: // 調用updateInteractiveTransition來更新動畫進度 // 裏面嵌套定義 percentForGesture 方法計算動畫進度 [self updateInteractiveTransition:[self percentForGesture:gestureRecognizer]]; break; case UIGestureRecognizerStateEnded: //判斷手勢位置,要大於通常,就完成這個轉場,要小於一半就取消 if ([self percentForGesture:gestureRecognizer] >= 0.5f) // 完成交互轉場 [self finishInteractiveTransition]; else // 取消交互轉場 [self cancelInteractiveTransition]; break; default: [self cancelInteractiveTransition]; break; } } // 計算動畫進度 -(CGFloat)percentForGesture:(UIScreenEdgePanGestureRecognizer *)gesture{ UIView * transitionContainerView = self.transitionContext.containerView; // 手勢滑動 在transitionContainerView中 的位置 // 這個位置判斷的方法能夠具體根據你的需求肯定 CGPoint locationInSourceView = [gesture locationInView:transitionContainerView]; CGFloat width = CGRectGetWidth(transitionContainerView.bounds); CGFloat height = CGRectGetHeight(transitionContainerView.bounds); if (self.edge == UIRectEdgeRight) return (width - locationInSourceView.x) / width; else if (self.edge == UIRectEdgeLeft) return locationInSourceView.x / width; else if (self.edge == UIRectEdgeBottom) return (height - locationInSourceView.y) / height; else if (self.edge == UIRectEdgeTop) return locationInSourceView.y / height; else return 0.f; }
上面的代碼有幾個點說一下:
一、你們注意一下初始化的時候咱們使用一個手勢去接收傳遞到咱們SwipTransitionInteractionController的手勢,這也是下面手勢事件可以執行的緣由;
二、這個startInteractiveTransition方法是咱們UIViewControllerInteractiveTransitioning協議裏面的方法,這個最主要的功能及時獲取咱們的交互上下文
self.transitionContext = transitionContext 也就是這句代碼
三、再有一點就是咱們的gestureRecognizeDidUpdate手勢方法裏面的更新進度以及取消和完成了,也就這幾個地方你們須要注意點;
NOTE: 看看下面的打印日誌
fromView = nil
但咱們的fromViewController.View 確實是存在的,在上面的Demo中你能夠看一下打印,確實是這樣:之前看博客有同行說:
UIView * fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIView * fromView = fromViewController.View 是等價的,這樣說應該吃不成立的。
而後在這裏:TransitionAnimation 學習筆記 開頭給出了答案,再理解一下。
Demo的下載地址這裏再發一次: 這裏是Demo的下載地址
END:
因爲篇幅的緣由,剩下的幾點要素咱們在下一篇當中接着說,固然你看到這篇文章的時候,第二篇我也確定是總結完了的。否則會看着斷片的。就像前面說的那樣,在第二篇當中多給一些實際的例子給你們參考。共同窗習。
最後就是咱們的Telegram羣的事,要是有須要的夥伴,我建立了一個Telegram iOS 交流羣,你要是對Telegram有興趣,或者是有什麼問題的,能夠在羣裏來找我,在我博客分類中有一個系列是專門說這個Telegram,最基本的編譯過程裏面有,你們能夠參考一下。系列的文章我確定會接着更新的。
羣號粘貼這裏:485718322
iOS 轉場動畫探究(二) 咱們接着看..........