iOS开发:自定义转场动画

iOS

写在前面

转场动画(Transitioning Animation)顾名思义,是场景/界面转换的一种过渡动画;系统默认的push/pop/present以及iOS的相册浏览其实都是系统实现的转场过渡动画。本文主要是对自定义转场动画的一个梳理及说明。

先来看看iOS7之前系统提供的转场动画API:

1
2
3
4
5
6
7
[container transitionFromViewController:from
toViewController:to
duration:0.25
options:0
animations:^{
} completion:^(BOOL finished) {
}];

API注释如下:

Transitions between two of the view controller’€™s child view controllers.
This method adds the second view controller’€™s view to the view hierarchy and then performs the animations defined in your animations block. After the animation completes, it removes the first view controller’€™s view from the view hierarchy.

可以得知,这是对容器控制器控制子控制器视图进行切换的转场动画方法。其中,container是容器视图控制器,fromtocontainer的子视图控制器,animation是具体动画的实现代码块,completion是动画的完成回调块。但是这个限制了,一是动画只能提交到animation中、没有可交互特性,二是动画效果没有被重用,所有container进行转场时都需要实现一遍animation,或者需要另外封装转场的API。

iOS7后公开的的自定义转场动画API,可以理解为是对上面转场方法的拓展,功能的分离跟封装。导航栏、tabbar控制器以及modal转场都是系统内置的转场动画器管理,本文以modal转场介绍为主。

基本概念与API

iOS7之后,Apple提供了自定义转场动画的<UIViewTransitioningDelegate>系列接口。我们需要清楚基本转场概念,来更好理解这套API。

转场也是因为iOS的单视窗的机制,在需要展示新场景的UI时,必须将新场景的视图结构添加到应用窗口中。举个例子,在iOS7以前做类似微博图片点击放大预览效果,通常的做法,一是将预览视图直接添加到window上(动画简单、事件管理麻烦),二是将预览视图控制器引入到新的window视图结构中(动画麻烦、存在多window管理问题)。实际上更好的方式是像原生的push/present那样,将视图控制器纳入当前的视图结构管理中。

可以再看一遍上文引用的视图转场API及文档描述,这是转场的核心概念。所谓“转场”(transition),是场景的过渡,是容器控制器对其子控制器视图切换的管理。

iOS7以一套协议的方式暴露了转场的接口,主要提供了以下几个方面的动画控制:

  • 动画控制,主要负责界面切换的(非交互式)动画;
  • 交互控制,主要负责交互管理(如手势管理)、基于交互去驱动动画;
  • 自定义展示样式控制,继承UIPresentationController进行控制,默认的界面展示是铺满全屏的,这里提供自定义接口;

这三个方面是独立的(但可以组合),针对不同控制层面提供的转场管理。(有些文章的介绍中将这些作为并列概念似乎不太合适)

这一套主要包含以下几个协议/概念:

  • Delegate:
  • Animator:
  • InterativeControllerr:
    • PercentInteractiveController:UIPercentDrivenInteractiveTransition
  • Context:
  • Coordinator:

联系文章开头列举的API,考虑下如果由我们自己来实现自定义转场动画的基础API,我们需要处理什么、怎样提供接口给开发者?以下是相关对象的详细行为规范:

Context对象,遵循context对象的存在因为动画过程被重用,它提供了动画需要能获取的动画上下文数据(如容器视图、切换前后场景视图、视图控制器等参数,参照文章开始的转场API);而对于动画过程中的参数获取,则由Coordinator动画协调器对象提供。

Delegate,转场的委托,主要是提供场景切换使用的animatorinteractiveControllerpresentationController,需要实现以下协议:

  • - (nullable id )animationControllerForPresentedController: presentingController: sourceController:
  • -(nullable id )animationControllerForDismissedController:
  • -(nullable id )interactionControllerForPresentation:(id )animator;
  • -(nullable id )interactionControllerForDismissal:(id )animator;
  • -(nullable UIPresentationController )presentationControllerForPresentedViewController:(UIViewController )presented presentingViewController:(nullable UIViewController )presenting sourceViewController:(UIViewController )source NS_AVAILABLE_IOS(8_0);
  • -(void)completeTransition:(BOOL)didComplete;

对于自定义样式的场景展示,需要实现上面第5个方法,提供presentationController控制器;对于需要交互式转场的,需要提供interactiveController控制器。

Animator,动画器对象,遵循协议,主要用于确定动画时间(非交互式动画)及执行具体场景切换动画:

  • -(NSTimeInterval)transitionDuration:(nullable id )transitionContext;
  • -(void)animateTransition:(id )transitionContext;

这是提供转场动画的具体实现位置,并且仅对于非百分比驱动的交互式转场允许空实现。另外需特别注意,一般在animator执行转场动画后,需调用context对象的-completeTransition:方法来结束转场状态,否则转场可能导致视图结构错误(比如被一个TransitionView覆盖到window顶部、导致底下视图无法接收事件);而对于纯交互式转场,则由interactiveController调用该方法。

interactiveController,可交互动画对象,遵循,基本协议约束的是交互式动画开始的调用:

  • -(void)startInteractiveTransition:(id )transitionContext;

context对象提供了更新进度的接口。另外,系统封装了百分比驱动的交互动画对象:UIPercentDrivenInteractiveTransition,实现了对CA动画的可逆的进度控制(具体原理需参考Core Animation的时间系统)。主要方法:

  • -(void)updateInteractiveTransition:(CGFloat)percentComplete;
  • -(void)finishInteractiveTransition;
  • -(void)cancelInteractiveTransition;

一般而言,我们需要做的,是在手势/事件回调中,进行进度的更新及转场的结束/取消操作。

调用逻辑

<UIViewControllerTransitioning>中的接口注释,解释了转场过程中涉及的基本调用逻辑,摘抄如下:

A transition context object is constructed by the system and passed to the animator in its animateTransition: and transitionDuration: methods as well as to the interaction controller in its startInteractiveTransition: method.

If there is an interaction controller its startInteractiveTransition: is called first and its up to the the interaction controller object to call the animateTransition: method if needed.

If there is no interaction controller, then the system automatically invokes the animator’s animateTransition: method.

The system queries the view controller’s transitioningDelegate or the navigation controller’s delegate to determine if an animator or interaction controller should be used in a transition. The transitioningDelegate is a new propery on UIViewController and conforms to the UIViewControllerTransitioningDelegate protocol defined below. The navigation controller likewise has been augmented with a couple of new delegate methods.

The UIViewControllerContextTransitioning protocol can be adopted by custom container controllers. It is purposely general to cover more complex transitions than the system currently supports. For now, navigation push/pops and UIViewController present/dismiss transitions can be customized. Information about the transition is obtained by using the viewControllerForKey:, initialFrameForViewController:, and finalFrameForViewController: methods. The system provides two keys for identifying the from view controller and the to view controller for navigation push/pop and view controller present/dismiss transitions.

All custom animations must invoke the context’s completeTransition: method when the transition completes. Furthermore the animation should take place within the containerView specified by the context. For interactive transitions the context’s updateInteractiveTransition:, finishInteractiveTransition or cancelInteractiveTransition should be called as the interactive animation proceeds. The UIPercentDrivenInteractiveTransition class provides an implementation of the UIViewControllerInteractiveTransitioning protocol that can be used to interactively drive any UIView property animations that are created by an animator.

目前支持导航栏控制器的push/pop以及控制器的present/dismiss转场的自定义。来实例看下执行控制器A->控制器B的自定义动画转场调用逻辑(Present方式):

  1. 设置B.transitionDelegate = Delegate
  2. A以animated=true的方式调用-presentViewController:animated:completion

回头看上面的文档说明,

首先系统向Delegate(对于present方式是transitionDelegate,对于navigation/tab是其delegate)询问,是否存在转场动画器(Animator)或者交互控制器(InteractiveAnimator)(另外还有自定义present样式的UIPresentationController);

  1. 若存在InterativeAnimator,则调用-startInteractiveTransition:携带上下文参数Context对象,开始交互式动画转场(留意API,在询问获取interactiveController时,是有参数animator的);否则
  2. 若存在Animator,则调用-animateTransition:携带Context对象,直接执行动画转场,动画在该方法中定义;若不使用自定义PresentationController,则视图结构关系与相应的presentationStyle相关(下文有提到),一般需要处理presented控制器的视图添加/移除。

对于Animator,动画应当在容器视图之内执行;对于交互式控制器,则应当更新执行进度(update/cancel/finish,这是基于手势/触控事件的);所有动画必须调用-completeTransition结束转场。Context对象实际则由自定义容器控制器Container所维护(系统提供)。

以上是iOS7新增的自定义转场的全部内容,可以按“调用逻辑”进行相关配置、提供相应的控制对象即可。

上面提到的,转场其实涉及的是视图场景的切换,因此附带上视图结构等转场前提知识介绍。

Present转场基本知识

基本知识可以查看Apple文档说明,几个知识点列举如下:

  1. 视图控制器结构:文档
  2. Present视图控制器:文档
  3. 自定义转场动画:文档
  4. 自定义转场样式:文档
视图控制器结构

视图控制器间的关系,决定了视图控制器的行为,UIKit为我们默认预定义了以下的视图控制器结构关系:

  1. 根视图控制器:通过UIWindow关联的rootViewController,将根视图控制器内容铺满窗口;

  2. 容器视图控制器:通过子控制器及自定义视图组织复杂界面,主要包括UINavigationControllrUITabBarControllerUIPageViewControllerUISplitViewController,容器控制器主要是管理子视图,但子视图控制器对其无依赖;对于如何自定义一个容器视图控制器,传送门在这里,简单来说就是,建立parent-child关系、实现转场动画以及事件传递;

  3. Presented视图控制器:是指以模态(modal)的方式展示新的视图内容(通常也移除旧的内容,具体下文有描述)。在Presentation时,通常UIKit查询到最近的容器控制器来提供上下文信息(context),或者可以主动告诉UIKit哪个视图控制器提供context及处理Presentation过程;

主要图示如下:

容器视图控制器Present结构

展示视图控制器:展示及转场过程

展示过程,主要是如何将一个新的内容呈现到屏幕上的过程,涉及几个方面的配置:

  • 展示样式(Presentation Style)
  • 转场样式(Transition Style)

展示样式主要包括:

  1. fullscreen:全屏
  2. pagesheet:这是一个scaleToFit的样式
  3. formsheet:样式同iPad上Appstore下载弹出页面
  4. 当前context:与1的区别在于不一定全屏
  5. OverXXX:区别于无Over的是,展示控制器内容不会被系统主动移出界面
  6. popover:样式同iPad的PopOver(OverXXX及Popover等部分样式是iOS8后提供的)
  7. 自定义样式:可控制展示视图大小以及presenting控制器内容是否移出界面等

转场样式,也就是内置动画样式,系统主要提供:

  1. .CoverVertical,这是默认的从屏幕底部弹上覆盖最终区域的动画样式
  2. .CrossDissolve,溶入动画

系统不可能提供应用业务所需的所有动画,这是开放转场动画的原因之一吧。

实例

我们简单实现一个相册图片预览的缩放转场动画,并添加交互手势支持,最后使用自定义展示控制器。基本实现都比较简单,但对于理解自定义转场基本原理正好足够了。为了明确基本逻辑,我们将transitioningDelegateanimatorinteractiveController以及presentationController都分离来实现,实际上也可以合并到一起减少参数传递。三个示例是逐步添加内容完善。

实例1 动画转场

缩放动画式Presentation。

实例2 交互式转场

支持交互式dismissal。

实例3 自定义展示控制器

添加淡入淡出黑色背景。

懒得贴代码了。。还是看demo吧(到时统一po到github)。

Author: Jason

Permalink: http://blog.knpc21.com/ios/ios-view-transitioning-animation/

文章默认使用 CC BY-NC-SA 4.0 协议进行许可,使用时请注意遵守协议。

Comments