objc系列译文(12.6):交互式动画

729 查看

在2007年,乔布斯在第一次介绍 iPhone 的时候,iPhone 的触摸屏交互简直就像是一种魔法。最好的例子就是在他第一次滑动 TableView 的展示上。你可以感受到当时观众的反应是多么惊讶,但是对于现在的我们来说早已习以为常。在展示的后面一部分,他特别指出当他给别人看了这个滑动例子,别人说的一句话: “当这个界面滑动的时候我就已经被征服了”.

是什么样的滑动能让人有‘哇哦’的效果呢?

滑动是最完美地展示了通过触摸屏直接操作的例子。滚动视图遵从于你的手指,当你的手指离开屏幕的时,视图会自然地继续滑动直到该停止的时候停止。它用自然的方式减速,甚至在快到界限的时候也能表现出细腻的弹力效果。滑动在任何时候都保持相应,并且看上去非常真实。

动画的状态

在 iOS 中的大部分动画仍然没有按照最初 iPhone 指定的滑动标准实现。这里有很多动画一旦它们运行就不能交互(比如说解锁动画,主界面中打开文件夹和关闭文件夹的动画,和导航栏切换的动画,还有很多)。

然而现在有一些应用给我一种始终在控制动画的体验,我们可以直接操作那些我在用的动画。当我们将这些应用和其他的应用相比较之后,我们就能感觉到明 显的区别。这些应用中最优秀的有最初的 Twitter iPad app, 和现在的 Facebook Paper。但目前,使用直接操作为主并且可以中断动画的应用仍然很少。这就给我们做出更好的应用提供了机会,让我们的应用有更不同的,更高质量的体验。

真实交互式动画的挑战

当我们用 UIView 或者 CAAnimation 来实现交互式动画时会有两个大问题: 这些动画会将你在屏幕上的内容和 layer 上的实际的特定属性分离开来,并且他们直接操作这些特定属性。

模型 (Model) 和显示 (Presentation) 的分离

Core Animation 是通过分离 layer 的模型属性和你在屏幕上看到的界面 (显示层) 的方式来设计的,这就导致我们很难去创建一个可以在任何时候能交互的动画,因为在动画时,模型和界面已经不能匹配了。这时,我们不得不通过手动的方式来同 步这两个的状态,来达到改变动画的效果:

直接控制 vs 间接控制

CAAnimation 动画的更大的问题是它们是直接在 layer 上对属性进行操作的。这意味着什么呢?比如我们想指定一个 layer 从坐标为 (100, 100) 的位置运动到 (300, 300) 的位置,但是在它运动到中间的时候,我们想它停下来并且让它回到它原来的位置,事情就变得非常复杂了。如果你只是简单地删除当前的动画然后再添加一个新 的,那么这个 layer 的速率就会不连续。

abruregregergerhght1pt

然而,我们想要的是一个漂亮的,流畅地减速和加速的动画。

smoregregehghrjt2oth

只有通过间接操作动画才能达到上面的效果,比如通过模拟力在界面上的表现。新的动画需要用 layer 的当前速度矢量作为参数传入来达到流畅的效果。

看一下 UIView 中关于弹簧动画的 API (animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:),你会注意到速率是个 CGFloat。所以当我们给一个移动 view 的动画在其运动的方向上加一个初始的速率时,你没法告知动画这个 view 现在的运动状态,比如我们不知道要添加的动画的方向是不是和原来的 view 的速度方向垂直。为了使其成为可能,这个速度需要用向量来表示。

解决方案

让我们看一下我们怎样来正确实现一个可交互并且可以中断的动画。我们来做一个类似于控制中心板的东西来实现这个效果:

 

这个控制板有两个状态:打开和关闭。你可以通过点击来切换这两个状态,或者通过上下拖动来调调整它向上或向下。我要将这个控制面板的所有状态都做到 可以交互,甚至是在动画的过程中也可以,这是一个很大的挑战。比如,当你在这个控制板还没有切换到打开状态的动画过程中,你点击了它,那么它应该从现在这 个点的位置马上回到关闭状态的位置。在现在很多的应用中,大部分都是用默认的动画 API,你必须要等一个动画结束之后你才能做自己想做的事情。或者,如果你不等待的话,就会看到一个不连续的速度曲线。我们要解决这个问题。

UIKit 力学

随着 iOS7 的发布,苹果向我们展示了一个叫 UIKit 力学的动画框架 (可以参见 WWDC 2013 sessions 206221)。UIKit 力学是一个基于模拟物理引擎的框架,只要你添加指定的行为到动画对象上来实现 UIDynamicItem 协议就能实现很多动画。这个框架非常强大,并且它能够在多个物体间启用像是附着和碰撞这样的复杂行为。请看一下 UIKit Dynamics Catalog,确认一下什么是可用的。

因为 UIKit 力学中的的动画是被间接驱动的,就像我在上面提到的,这使我们实现真实的交互式动画成为可能,它能在任何时候被中断并且拥有连续的加速度。同 时,UIKit 力学在物理层的抽象上能完全胜任我们一般情况下在用户界面中的所需要的所有动画。其实在大部分情况下,我们只会用到其中的一小部分功能。

定义行为

为了实现我们的控制板的行为,我们将使用 UIkit 力学中的两个不同行为:UIAttachmentBehaviorUIDynamicItemBehavior。附着行为用来扮演弹簧的角色,它将界面向目标点拉动。另一方面,我们用动态 item behvaior 定义了比如摩擦系数这样的界面的内置属性。

我创建了一个我们自己的行为子类,以将这两个行为封装到我们的控制板上:

我们通过一个 dynamic item 来初始化这个行为,然后就可以设置它的目标点和我们想要的任何速度。在内部,我们创建了附着行为和 dynamic item 行为,并且将这些行为添加到我们自定义的行为中:

为了用 targetPointvelocity 属性来影响 item 的 behavior,我们需要重写它们的 setter 方法,并且分别修改在附着行为和 item behaviors 中的对应的属性。我们对目标点的 setter 方法来说,这个改动很简单:

对于 velocity 属性,我们需要多做一些工作,因为 dynamic item behavior 只允许相对地改变速度。这就意味如果我们要将 velocity 设置为绝对值,首先我们就需要得到当前的速度,然后再加上速度差才能得到我们的目标速度。