在iOS7以前,開發者如果希望定制導航控制器推入推出視圖時的轉場動畫,一般都只能通過子類化UINavigationController或者自己編寫動畫代碼去覆蓋相應的方法,現在iOS7為開發者帶來了福音,蘋果公司引入了大量新API,給予了開發者很高的自由度,在處理由UIViewController管理的UIView動畫時,這些API使用方便,可擴展性也很強,定制起來非常輕松:
? 全新的針對UIView的動畫block方法
? 全新的UIViewControllerAnimatedTransitioning協議以及動畫控制器的概念
? Interaction Controllers以及Transition Coordinators
? 全新的針對動畫的助手API(簡便方法)
這里我編寫了一個示例應用程序,其中展示了我將在這篇文章中所提到的一些技巧, 為了快速理解我們應當如何使用iOS7的新API來處理
UIViewController的轉場動畫,請在此鏈接中下載該示例。
| | /cms/uploads/soft/131224/4673-131224114628.zip |
全新的針對UIView的動畫block方法
iOS4的發布帶來了強大的block方法,在編寫UIView動畫時使用block可以輕松地得到滿意的效果,然而有些情況下,我們還是不得不直接使用Core Animation。幸運的是,蘋果公司在iOS7中增加了2個新的基于block的方法,這樣我們就很少再需要直接使用Core Animation了。
關鍵幀動畫
iOS7為UIView封裝了一組API,讓我們很容易的得到與Core Animation框架中的CAKeyframeAnimation一樣的效果。
[UIView?animateKeyframesWithDuration:duration?delay:delay?options:options?animations:^{?[UIView?addKeyframeWithRelativeStartTime:0.0?relativeDuration:0.5?animations:^{??}];?[UIView?addKeyframeWithRelativeStartTime:0.5?relativeDuration:0.5?animations:^{??}];?}?completion:^(BOOL?finished)?{??}];?
新引入的animateKeyframesWithDuration與CAKeyframeAnimation的關系,可以比對animateWithDuration和CABasicAnimation,我們只需要將每一幀動畫加入到block方法中,并傳入此段動畫在全過程中的相對開始時間和執行時間(duration具體是指此段動畫的執行時間占全過程的百分比)。同時,你可以在一次動畫中使用多個關鍵幀,只需使用addKeyframe依次將所有關鍵幀加入動畫執行棧中。
下面是一個簡單的例子:在示例應用中,我使用關鍵幀block來退出模態視圖控制器。
[UIView?addKeyframeWithRelativeStartTime:0.0?relativeDuration:0.15?animations:^{??snapshot.transform?=?CGAffineTransformMakeRotation(M_PI?*?-1.5);?}];?[UIView?addKeyframeWithRelativeStartTime:0.15?relativeDuration:0.10?animations:^{??snapshot.transform?=?CGAffineTransformMakeRotation(M_PI?*?1.0);?}];?[UIView?addKeyframeWithRelativeStartTime:0.25?relativeDuration:0.20?animations:^{??snapshot.transform?=?CGAffineTransformMakeRotation(M_PI?*?1.3);?}];?[UIView?addKeyframeWithRelativeStartTime:0.45?relativeDuration:0.20?animations:^{??snapshot.transform?=?CGAffineTransformMakeRotation(M_PI?*?0.8);?}];?[UIView?addKeyframeWithRelativeStartTime:0.65?relativeDuration:0.35?animations:^{???CGAffineTransform?shift?=?CGAffineTransformMakeTranslation(180.0,?0.0);?CGAffineTransform?rotate?=?CGAffineTransformMakeRotation(M_PI?*?0.3);?snapshot.transform?=?CGAffineTransformConcat(shift,?rotate);?_coverView.alpha?=?0.0;?}];?
視圖仿佛在重力的牽引下繞左下角順時針旋轉,并在最低點擺動了一下,最后脫落。
彈簧動畫
iOS7新引入的另一個block方法可以讓你輕松將真實物理世界中的彈性效果集成進視圖動畫中。蘋果公司一直建議開發者盡可能將動畫效果做的跟真實物理世界一樣——在視圖滑動時,可以像彈簧一樣,稍微拉伸一些,再彈回正確位置。使用新的彈簧動畫API來實現此效果相較以往要簡單很多。
[UIView?animateWithDuration:duration?delay:delay?usingSpringWithDamping:damping?initialSpringVelocity:velocity?options:options?animations:^{??}?completion:^(BOOL?finished)?{??}];?
這里用到了一些物理上的概念:damping參數代表彈性阻尼,隨著阻尼值越來越接近0.0,動畫的彈性效果會越來越明顯,而如果設置阻尼值為1.0,則視圖動畫不會有彈性效果——視圖滑動時會直接減速到0并立刻停止,不會有彈簧類的拉伸效果。
velocity參數代表彈性修正速度,它表示視圖在彈跳時恢復原位的速度,例如,如果在動畫中視圖被拉伸的最大距離是200像素,你想讓視圖以100像素每秒的速度恢復原位,那么就設置velocity的值為0.5。(譯者:建議大家看看源代碼,代碼中damping設置為0.8不夠明顯,你可以將damping調為0.1,然后慢慢調整velocity看看效果)
在示例應用程序中,我用彈簧動畫讓模態視圖控制器從屏幕底部滑上來,設置彈性阻尼為0.8,彈性修正速度為1.0,運行后可以看到,視圖將沖出15像素的距離,然后慢慢降回原位。如果我設置彈性阻尼為0.6或者更小,那么視圖會沖得更高,而且降回原位前還會繼續向下反彈。(也就是停止前來回彈的次數越來越多,彈性效果越來越明顯)需要注意的是,不要將彈性動畫與UIKit的動態特效引擎相混淆。彈性動畫是一個標準的UIView動畫API,僅僅提供了有限的幾種真實物理效果。
自定義UIViewController的轉場動畫
現在讓我們來看一個好東西。蘋果公司不僅為開發者引入了新的動畫API,而且還擴大了其應用范圍。在使用UIViewController管理視圖的推入推出時,可以很容易地自定義以下轉場動畫:
? UIViewController
? presentViewController
? UITabBarController
? setSelectedViewController
? setSelectedIndex
? UINavigationController
? pushViewController
? popViewController
? setViewControllers
在示例應用程序中,我創建了一系列轉場動畫,在動畫中使用了之前講解過的新引入的彈簧動畫和關鍵幀block方法,現在讓我們來看看如何使用新API來自定義上述的轉場動畫。
核心概念:動畫控制器
那么,如何在使用自定義動畫的同時不影響視圖的其他屬性?對此蘋果公司提供了一個新的協議:UIViewControllerAnimatedTransitioning,我們可以在協議方法中編寫自定義的動畫代碼。蘋果開發者文檔中稱實現了此協議的對象為動畫控制器。
由于我們使用了協議這一語法特性,自定義動畫的代碼可以靈活的放在自己想要的位置。你可以創建一個專門用于管理動畫的類, 也可以讓UIViewController實現UIViewControllerAnimatedTransitioning接口。由于需要實現一系列不同的動畫,因此選擇為每個動畫創建一個類。接下來創建這些動畫類的通用父類——BaseAnimation,它定義了一些通用的屬性和助手方法。
讓我們來看第一個動畫,使用UINavigationController推入推出視圖時,會有一個簡單的縮放效果。
-(void)animateTransition:?(id)transitionContext?{??UIView?*containerView?=?[transitionContext?containerView];?UIViewController?*fromViewController?=?[transitionContext?viewControllerForKey:UITransitionContextFromViewControllerKey?];?UIViewController?*toViewController?=?[transitionContext?viewControllerForKey:UITransitionContextToViewControllerKey];?if?(self.type?==?AnimationTypePresent)?{??toViewController.view.transform?=?CGAffineTransformMakeScale(0.0,?0.0);?[containerView?insertSubview:toViewController.view?aboveSubview:fromViewController.view];??[UIView?animateWithDuration:[self?transitionDuration:transitionContext]?animations:^{?toViewController.view.transform?=?CGAffineTransformMakeScale(1.0,?1.0);?}?completion:^(BOOL?finished)?{?[transitionContext?completeTransition:YES];?}];?}?else?if?(self.type?==?AnimationTypeDismiss)?{??[containerView?insertSubview:toViewController.view?belowSubview:fromViewController.view];??[UIView?animateWithDuration:[self?transitionDuration:transitionContext]?animations:^{?fromViewController.view.transform?=?CGAffineTransformMakeScale(0.0,?0.0);?}?completion:^(BOOL?finished)?{?[transitionContext?completeTransition:YES];?}];?}?}?-(NSTimeInterval)transitionDuration:?(id)transitionContext?{?return?0.4;?}?
符合UIViewControllerAnimatedTransitioning協議的任何對象都需要實現animateTransition:和transitionDuration:兩個方法。你也可以選擇實現@optional方法animationEnded:,它在動畫完成后由系統自動調用,相當于completion block,非常方便。
在animateTransition:中你需要處理以下過程:
1. 將“to”視圖插入容器視圖
2. 將“to”和“from”視圖分別移動到自己想要的位置
3. 最后,在動畫完成時千萬別忘了調用completeTransition: 方法
UIViewControllerAnimatedTransitioning協議中的方法都帶有一個參數:transitionContext,這是一個系統級的對象,它符合 UIView-ControllerContextTransitioning協議,我們可以從該對象中獲取用于控制轉場動畫的必要信息,主要包括以下內容:
顯然,蘋果公司幫助開發者完成了大部分讓人討厭的細節工作,僅僅需要我們自己完成的工作就是定義動畫的初始狀態和終止狀態,并調整到自己滿意的效果。最后我再啰嗦兩句有關transitionContext的重要注意事項:
1.獲取frame的方法可能會返回CGRectZero——如果系統無法確定該frame的值具體是什么。例如,如果你使用自定義的模態視圖控制器
推出動畫,在結束時系統無法確定其finalFrame。
2.如果視圖控制器已經從屏幕上移除了,那么獲取frame的方法也會返回CGRectZero。例如在導航控制器的轉場動畫結束后,試圖獲取“from”視圖的finalFrame。
你不用手動去移除“from”視圖,transitionContext將自動幫你完成。
3.如果你在應用的其他地方需要使用transitionContext,你可以放心地使用動畫控制器保留一個transitionContext的引用。
將動畫控制器應用到轉場動畫中。
現在,我們已經開發好了動畫控制器,那么最后需要做的就是,將它們應用到轉場動畫中:我們需要對管理轉場動畫的UIViewController做一些操作。
一般來說,我們只需要讓UIViewController符合UIViewController-TransitioningDelegate 協議, 編寫animationController-ForPresentedController和animationControllerForDismissedController方法。在我的示例應用程序中,我設置了一個屬性,用來讓動畫控制器知道目前正在推入還是推出視圖:
-(id)?animationControllerForPresentedController:(UIViewController?*)presented?presentingController:(UIViewController?*)presenting?sourceController:(UIViewController?*)source?{?modalAnimationController.type?=?AnimationTypePresent;?return?modalAnimationController;?}?-(id)?animationControllerForDismissedController:(UIViewController?*)dismissed?{?modalAnimationController.type?=?AnimationTypeDismiss;?return?modalAnimationController;?}?
然后,在推入模態視圖控制器時,我們設置modalPresentationStyle為UIModalPresentationFullScreen或UIModalPresentationCustom。我們還必須將一個符合UIViewControllerTransitioningDelegate協議的對象設置為它的transitioningDelegate,一般來說都是推入該模態視圖控制器的UIViewController。
OptionsViewController?*modal?=?[[OptionsViewController?alloc]?initWithNibName:@"OptionsViewController"?bundle:[NSBundle?mainBundle]];?modal.transitioningDelegate?=?self;?modal.modalPresentationStyle?=?UIModalPresentationCustom;?[self?presentViewController:modal?animated:YES?completion:nil];?
如果需要將動畫控制器應用到UINavigationController的轉場動畫中,我們需要使用UINavigationControllerDelegate協議中的一個新方法:animationControllerForOperation。對于任何自定義的導航轉場動畫,導航欄都會有一個淡入淡出的動畫過程。同樣,對于UITabBarController,使用UITabBarControllerDelegate協議的新方法——animationController-ForTransitionFromViewController。
為轉場動畫定義交互方式
在iOS7中,蘋果到處都在使用交互式彈出手勢,同時,蘋果也給開發者們提供了一系列工具,只需簡單幾步就能將交互手勢應用在視圖切換過程中。我們可以通過相應的委托方法返回一個交互控制器:
? UINavigationController
? interactionControllerForAnimationController
? UITabBarController
? interactionControllerForAnimationController
? UIViewController
? interactionControllerForPresentation
? interactionControllerForDismissal
這里唯一需要注意的是,如果沒有自定義轉場動畫,這些方法就不會起作用。例如,你必須從animationControllerForOperation得到一個有效的動畫控制器,UINavigationController才會調用interactionController-
ForAnimationController——即使你在轉場交互中沒有使用動畫控制器。
其次,交互控制器非常靈活,有很強的可擴展性。雖然在示例應用程序中我使用手勢檢測來控制交互,但是你也可以用手勢以外的其他方式來實現。你可以設計任意你想要的效果用以轉場交互。
交互控制器:最簡單的實現方式有兩種方式可以創建交互控制器。第一個也是最簡單的一個,就是使用UIPercentDrivenInteractiveTransition。
@interface?UIPercentDrivenInteractiveTransition?:?NSObject??@property?(readonly)?CGFloat?duration;?@property?(readonly)?CGFloat?percentComplete;?@property?(nonatomic,assign)?CGFloat?completionSpeed;?@property?(nonatomic,assign)?UIViewAnimationCurve?completionCurve;?-?(void)updateInteractiveTransition:(CGFloat)percentComplete;?-?(void)cancelInteractiveTransition;?-?(void)finishInteractiveTransition;?
這個類具體實現了UIViewControllerInteractiveTransitioning協議,我們可以使用它輕松為動畫控制器添加自定義的交互方式。只要為目標視圖加入手勢(或者其他交互方式)并調用updateInteractiveTransition:,傳入動畫時間占整個過程的百分比即可。同時, 記住在交互完成后調用finishInteractiveTransition: , 交互被取消時調用cancel-InteractiveTransition:。下面的例子展示了如何將捏合手勢應用到轉場動畫中:
-(void)handlePinch:(UIPinchGestureRecognizer?*)pinch?{?CGFloat?scale?=?pinch.scale;?switch?(pinch.state)?{?case?UIGestureRecognizerStateBegan:?{?_startScale?=?scale;?self.interactive?=?YES;?[self.navigationController?popViewControllerAnimated:YES];?break;?}?case?UIGestureRecognizerStateChanged:?{?CGFloat?percent?=?(1.0?-?scale/_startScale);?[self?updateInteractiveTransition:(percent?<?0.0)???0.0?:?percent];?break;?}?case?UIGestureRecognizerStateEnded:?{?CGFloat?percent?=?(1.0?-?scale/_startScale);?BOOL?cancelled?=?([pinch?velocity]?<?5.0?&&?percent?<=?0.3);?if?(cancelled)?[self?cancelInteractiveTransition];?else?[self?finishInteractiveTransition];?break;?}?case?UIGestureRecognizerStateCancelled:?{?CGFloat?percent?=?(1.0?-?scale/_startScale);?BOOL?cancelled?=?([pinch?velocity]?<?5.0?&&?percent?<=?0.3);?if?(cancelled)?[self?cancelInteractiveTransition];?else?[self?finishInteractiveTransition];?break;?}?}?}?
當你繼承了UIPercentDrivenInteractiveTransition類,交互過程中系統會自動調用動畫控制器的animateTransition:方法,按照你傳遞的percentComplete參數實時地展現動畫效果。在交互完成后,它還自動調用animateTransition:方法恢復到正常狀態,一旦交互完成,我們就可以改變completionSpeed和completionCurve屬性來修改其他的一些樣式。
交互控制器:通過自定義的方式
如果你需要深入控制UIPercentDrivenInteractiveTransition處理轉場動畫的細節,那么就不用去繼承該類,而是使用UIViewController-InteractiveTransitioning協議。此協議與UIViewController-AnimatedTransitioning類似,我們可以通過該協議控制所有關于轉場動畫的細節。在該協議中我們需要完成以下步驟:
1. 實現startInteractiveTransition:方法,用于初始化專場動畫。
2. 獲取transitionContext 對象的引用(如果繼承了UIPercentDrivenInteractiveTransition,可以看到它自動幫我們完成了這一步驟,因此這里我們必須手動獲取該對象)。
3. 和之前一樣,在適當的情況下調用updateInteractiveTransition:,cancelInteractiveTransition和finishInteractiveTransition(對于導航控制器來說,完成方法中還需要顯示或隱藏導航欄)。
4. 完成后仍然請記住調用transitionCompleted:。
下面是我通過自定義的交互控制器來實現與之前相同的動畫,仍然是使用捏合手勢控制轉場動畫。
-(void)startInteractiveTransition:?(id)transitionContext?{??_context?=?transitionContext;??UIView?*containerView?=?[transitionContext?containerView];?UIViewController?*fromViewController?=?[transitionContext?viewControllerForKey:UITransitionContextFromViewControllerKey?];?UIViewController?*toViewController?=?[transitionContext?viewControllerForKey:UITransitionContextToViewControllerKey];??toViewController.view.frame?=?[transitionContext?finalFrameForViewController:toViewController];?[containerView?insertSubview:toViewController.view?belowSubview:fromViewController.view];??_transitioningView?=?fromViewController.view;?}?-(void)updateWithPercent:(CGFloat)percent?{?CGFloat?scale?=?fabsf(percent-1.0);?_transitioningView.transform?=?CGAffineTransformMakeScale(scale,?scale);?[_context?updateInteractiveTransition:percent];?}?-(void)end:(BOOL)cancelled?{?if?(cancelled)?{?[UIView?animateWithDuration:_completionSpeed?animations:^{?_transitioningView.transform?=?CGAffineTransformMakeScale(1.0,?1.0);?}?completion:^(BOOL?finished)?{?[_context?cancelInteractiveTransition];?[_context?completeTransition:NO];?}];?}?else?{?[UIView?animateWithDuration:_completionSpeed?animations:^{?_transitioningView.transform?=?CGAffineTransformMakeScale(0.0,?0.0);?}?completion:^(BOOL?finished)?{?[_context?finishInteractiveTransition];?[_context?completeTransition:YES];?}];?}?}?
你可以讓動畫控制器同時實現UIViewControllerInteractive-Transitioning和 UIViewControllerAnimatedTransitioning(像示例程序中那樣),從而把所有代碼都放在一個類中。你也可以將交互控制器和動畫控制器分成兩個類——協議這一語法特性的妙處在于,你可以輕松實現符合需求的最佳解決方案。
更多小技巧
在block中選擇是否進行動畫
開發者或許會遇到這樣一種情況:在一串精美的動畫效果中,我們需要讓某些視圖不進行動畫,從而營造一種動靜相宜的效果。在動畫block方法推出之前,我們可以在[UIView beginAnimations]和[UIView commitAnimations]之間使用setAnimationsEnabled方法來設置哪些動畫不需要執行。而在iOS7SDK中,蘋果公司為開發者提供了新方法,只要把不需要執行的動畫寫在block中即可:
[UIView?performWithoutAnimation:^{??}];?
你可以隨時執行這段代碼來控制不需要執行的動畫。
集合視圖的導航轉場動畫
你可能對UICollectionView的setLayout:animated:方法非常熟悉了。在iOS7中,當導航控制器推入推出集合視圖控制器時,如果開啟了 useLayout-ToLayoutNavigationTransitions屬性,系統將自動調用setLayout:animated:方法。因此,在你推入集合視圖控制器時,只需要設置該屬性,導航控制器就可以自動執行動畫,和你手動對集合視圖調用setLayout:animated方法的效果一樣。
CollectionViewController?*VC?=?[[CollectionViewController?alloc]?initWithCollectionViewLayout:flowLayout];?VC.title?=?@"Mini?Apples";?VC.useLayoutToLayoutNavigationTransitions?=?YES;?[self.navigationController?pushViewController:VC?animated:YES];?
轉場動畫調度器
還有一個非常有用的API, 它可以幫助視圖控制器管理轉場動畫:UIViewControllerTransitionCoordinator協議。在iOS7中,每一個視圖控制器(當然也包括UINavigationController和UITabBarController)都有一個transitionCoordinator屬性,該屬性提供了一系列用于轉場動畫的強大工具,首先我們來看看animateAlongsideTransition:方法。
[self.transitionCoordinator?animateAlongsideTransition:^(id<uiviewcontrollertransitioncoo?< span="">rdinatorContext>?context)?{??}?completion:^(id?context)?{??}];?
我們可以通過這個方法在進行轉場動畫時并行執行一些其他動畫,context參數和之前提到的符合UIViewControllerContextTransitioning協議的transitionContext參數相類似,從該參數中我們可以獲取有關轉場過程的一些重要信息,包括container view和轉場效果。蘋果公司甚至允許開發者不傳入context參數,只傳入完成后執行的block。所以請大膽嘗試使用它吧。
對于交互轉場來說, 視圖在轉場過程中狀態可能發生改變, 于是notifyWhenInteractionEndsUsingBlock:方法特別有用——它可以用來管理視圖狀態。在交互轉場中,viewWillAppear:方法或許會在某個視圖控制器推入時被調用,但是按照常理隨后應該會被調用的viewDidAppear:則不一定,因為用戶隨時可能取消該交互(例如在之前的例子中,捏到一半又恢復原狀)。
由此,如果我們不希望在這種情況下修改視圖狀態,我們可以使用該方法,恢復對視圖的更改(使用UIViewControllerTransitionCoordinatorContext的isCancelled屬性)。
[self.transitionCoordinator?notifyWhenInteractionEndsUsingBlock:^(id<uiviewcontrollertran?< span="">sitionCoordinatorContext>?context)?{??}];?
屏幕快照
在iOS7 以前, 獲取一個UIView的快照有以下步驟: 首先創建一個UIGraphics的圖像上下文,然后將視圖的layer渲染到該上下文中,從而取得一個圖像,最后關閉圖像上下文,并將圖像顯示在UIImageView中。現在我們只需要一行代碼就可以完成上述步驟了:
[view?snapshotViewAfterScreenUpdates:NO];?
這個方法制作了一個UIView的副本,如果我們希望視圖在執行動畫之前保存現在的外觀,以備之后使用(動畫中視圖可能會被子視圖遮蓋或者發生其他一些變化),該方法就特別方便。
afterUpdates參數表示是否在所有效果應用在視圖上了以后再獲取快照。例如,如果該參數為NO,則立馬獲取該視圖現在狀態的快照,反之,以下代碼只能得到一個空白快照:
[view?snapshotViewAfterScreenUpdates:YES];?[view?setAlpha:0.0];?
由于我們設置afterUpdates參數為YES,而視圖的透明度值被設置成了0,所以方法將在該設置應用在視圖上了之后才進行快照,于是乎屏幕空空如也。另外就是……你可以對快照再進行快照……繼續快照……
結論
蘋果公司在iOS7中為開發者添加了新的用于創建和自定義動畫的API。iOS7 SDK不僅引入了強大的動畫block和許多易于使用的方法,而且徹底改變了為視圖自定義動畫的方式。最后,強烈建議你將示例應用程序的代碼下載下來,看看我在本文中介紹的API的使用方法!
| | /cms/uploads/soft/131224/4673-131224114628.zip |
總結
以上是生活随笔為你收集整理的控制器之间跳转实现转场动画,动画控制器概念的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。