在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:^{
- //順時針旋轉90度
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- -1.5);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.15
- relativeDuration:0.10 animations:^{
- //180度
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- 1.0);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.25
- relativeDuration:0.20 animations:^{
- //擺過中點,225度
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- 1.3);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.45
- relativeDuration:0.20 animations:^{
- //再擺回來,140度
- 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) {
- //插入“to”視圖,初始縮放值為0.0
- toViewController.view.transform =
- CGAffineTransformMakeScale(0.0, 0.0);
- [containerView insertSubview:toViewController.view
- aboveSubview:fromViewController.view];
- //縮放“to”視圖為想要的效果
- [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) {
- //插入“to”視圖
- [containerView insertSubview:toViewController.view
- belowSubview:fromViewController.view];
- //縮小“from”視圖,直到其消失
- [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 {
- //擷取transitionContext對象的引用
- _context = transitionContext;
- //擷取容器視圖引用
- UIView *containerView = [transitionContext
- containerView];
- UIViewController *fromViewController = [transitionContext
- viewControllerForKey:UITransitionContextFromViewControllerKey
- ];
- UIViewController *toViewController = [transitionContext
- viewControllerForKey:UITransitionContextToViewControllerKey];
- //插入“to”視圖
- 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的使用方法!
源連結:http://www.cocoachina.com/ios/20131224/7597.html