iOS交互式动画详解(下):iOS 10 的新变化

转自http://www.infoq.com/cn/articles/ios-interactive-animation-p2

不久前结束的WWDC 2016 Session 216: Advances in UIKit Animations and Transitions介绍了 iOS 10 的新动画 API,让动画与交互无缝连接,这是「开发者的大事、大快所有人心的大好事」。在上篇我探讨了 iOS 10 以下的系统中如何使用 UIView Animation 实现交互动画,本篇来探讨 iOS 10 带来的变化。

新 API 的改进

新 API 的核心是UIViewPropertyAnimator类,在UIViewAnimating协议中定义了交互动画需要的所有基础功能:暂停,恢复,停止,逆转动画以及控制动画进度。UIView Animation 并没有提供这些功能,这些功能都需要回到 Core Animation 作用的 CALayer 里使用分散且文档晦涩难懂的 API 来实现。UIViewImplicitlyAnimating协议主要补充了与 UIView Animation 类似的添加动画 Block 的方法。

UITimingCurveProvider协议重新封装了时间函数,而UISpringTimingParameters类终于带来了期待已久的两点改进:

完全版本的弹簧动画:iOS 7 引入了简化的 Spring UIView Animation API,iOS 9 引入了无文档的完全版本的 Spring Core Animation API;而这两个版本的初始速度皆为数值,iOS 10 的所有弹簧动画的速度都是向量。

UIViewPropertyAnimator类可以视为面向对象版本的 UIView Animation,以动画 Block 为基础的设计解决了多个 UIView 参与动画时的交互控制,而使用 UIView Animation 时面对多个视图参与交互动画就需要针对每个视图进行控制。

交互转场的最后一块拼图

在转场动画里,非交互转场与交互转场之间有着明显的界限:如果以交互转场开始,尽管在交互结束后会切换到非交互状态,但之后无法再次切换到交互状态,只能等待其结束;如果以非交互转场开始,在转场动画结束前是无法切换到交互控制状态的,只能等待其结束。iOS 10 在转场协议中引入了上述 API,这使得非交互转场与交互转场之间的界限不再泾渭分明。

让转场动画在非交互状态与交互状态之间自由切换很困难,UIViewPropertyAnimator类实现了需要的所有基础功能,使得难度降低了许多。在 session 的现场演示中,工程师演示了使用该类从头打造可全程在非交互与交互状态之间自由切换的转场动画。转场协议为了实现高度定制化,定义的方法是比较冗余的,iOS 10 在此基础上引入的新 API 使得协议更加复杂,虽然在演示中添加的代码只有百来行,另一方面演示的转场动画本身也相对复杂,使得这一切看上去很非常复杂。

事实上,依靠UIViewPropertyAnimator类,在实现转场动画在非交互与交互状态之间自由切换的基础上,还可以大幅精简现有的转场协议体系。但转场动画本身是个很繁杂的话题,展开讲将占用大量的篇幅,这部分具体内容我放在了「iOS 视图控制器转场详解」更新的章节里。转场动画本质上是相关视图控制器的转换,并将其中视图的转换使用动画的形式展现。除去控制器的部分,转场动画就与使用 UIView 下面这个方法来实现的的视图转换动画无异。

transitionFromView:toView:duration:options:completion:

objc.io 在「交互式动画」中探讨了如何让普通的动画实现交互,这与 iOS 10 对转场动画的改进是一脉相承的,因此接下来我将使用UIViewPropertyAnimator类来继续 objc.io 的探讨来深度讲解新 API。

新 API 实践

要实现的效果如下:

这个简单的位移动画里包含了两套交互:滑动控制(pan 手势)和点击控制(tap 手势),要解决三个转换问题,也是所有交互动画需要解决的问题:

Animation to Gesture:动画过程中切入滑动控制,需要中止当前的动画并由手指来控制控制板的移动;

Gesture to Animation:滑动结束后添加新的动画,并与当前的状态平滑衔接,这需要 Spring 动画;

Animation to Animation:动画过程中每次点击视图后使动画逆转。

前面提到UIViewPropertyAnimator封装了交互动画需要的所有基础功能,实现交互动画的难度大大降低了,这篇文章似乎没有写的必要了。以上每个转换问题该类都有几种解决办法,使用方法非常灵活,但相对地,复杂性增加了不少,也有不少地方需要注意。这次不像上篇中分别解决三个转换问题,而是将之归类为实现滑动控制和点击控制,并首先解决后者。

点击交互:逆转动画

先进行设置:

添加的 Animation Block 和 Completion Blcok 是一次性的,不会重复使用。接下来处理 Tap 手势:

上面的代码逆转动画的效果如同下面的 BeginFromCurrentState,而我们更需要的是更加自然的 Additive 效果,虽然在这个场景里,0.5s的动画时间无法看出这两种效果的差别:

实现 Additive 效果可以通过添加反向的动画来实现,使用 UIView Animation 时也是这样做来逆转动画:

//每次 Tap 手势结束后添加向反方向运动的动画animator.addAnimations({//targetY 为相反位置的坐标panelView.center.y = targetY })

为何不选择这种方法?不能仅仅为了展示UIViewPropertyAnimator不同于 UIView Animation 的特性而让效果打折,事实上,这是无奈之举:不知是否是 Bug,当 Spring Timing 的初始速度不为(0, 0)时,这种方法无法实现 Additive 效果,而是中止动画直接跳跃到最终位置,其他类型的 Timing 则没有这个问题,然而这个场景里的位移动画必须是带初始速度的 Spring 动画;不过即使此处不要求初始速度>0,通过添加反向动画实现 Additive 效果的做法也会有瑕疵,同样不知是否 Bug:最初添加的动画的运行时间截止时,如果依然添加动画,动画会直接跳跃到最终位置。

其实UIViewPropertyAnimator使用初始速度不为(0, 0)的 Spring Timing 也可以实现 Additive 效果,关键在于isInterruptible属性,默认为 true。禁用这个属性后,UIViewPropertyAnimator完全与 UIView Animation 无异,上段里提到的问题都不存在;然而,禁用这个属性后,UIViewAnimating协议里定义的与交互动画有关的方法和属性都不能使用:包括上面使用的暂停和逆转动画的功能,以及接下来会用到的停止动画的功能,禁用后使用这些方法和属性会触发异常。将UIViewPropertyAnimator当作 UIView Animation 使用的话,去看上篇就好了,我在文末给出的 Demo 里示范了这种用法。

综合来讲,UIViewPropertyAnimator逆转转动画的效果比不上 UIView Animation ,现在暂且带着效果打折的遗憾继续使用UIViewPropertyAnimator来实现滑动交互。

滑动交互:控制进度、平滑转变

当手指接触到视图时,如何中止当前的动画?UIViewPropertyAnimator给了我们两个选择:暂停或停止动画。在使用 UIView Animation 时,我们直接取消了视图的动画,也就是停止动画,这里选择用该类的方式来停止动画:

停止动画还有另外一种使用方法:

不管手指接触控制板视图时是否在运动中,手指离开屏幕后都需要添加新的弹簧动画。然而上面的方案在特定条件下有漏洞:假设此时控制板处于打开状态(底部位置),用户向上滑动来关闭控制板,滑动结束后控制板在动画中移往顶部位置,如果用户想取消这个操作,于是点击了控制板视图,那么控制板视图最终并不会回到底部位置,而是在中间某个位置(滑动结束时的位置)。造成这个结果的根源在于点击交互的实现手法:如果是通过添加反向的动画来实现逆转,那么就不会出现这个问题;而无论是出于展示新 API 特点的目的还是为了能够在这里使用stopAnimation:方法,我选择了使用isReversed属性来逆转动画。滑动结束后动画的起始位置是手指离开屏幕的位置,使用isReversed逆转动画最终只能回到这个位置,而这个位置肯定和控制板在打开/关闭状态所处的位置有段差距。

选择使用isReversed来逆转动画时,在所有连续类型的手势参与的交互动画里,使用stopAnimation:都会有这样的漏洞。完美的解决方案是在手指接触视图时将其暂停,不过不注意的话也会出现这样的漏洞:

使用pauseAnimation()能够解决这个漏洞的原因在于:在手势的起始阶段为控制板视图提供从底部位置到顶部位置的完整动画,逆转后始终能够回到正确的位置;而使用stopAnimation:时不能提供完整路径的动画。

如果不在手势的起始阶段就添加动画,而是在手势的结束阶段才添加动画,pauseAnimation()也会出现上述漏洞;另一方面,使用stopAnimation:无法在手势的变化阶段控制动画的进度,只能修改视图本身。从这两点考虑,实现转场动画以及在非交互与交互状态之间自由切换应该选择pauseAnimation()这条路线。

continueAnimation(withTimingParameters:durationFactor:)是UIViewImplicitlyAnimating协议定义的方法,这是保证交互动画流畅的关键,如同使用 UIView Animation 实现交互动画时 Spring Animation 的作用一样。这个方法将动画的起始位置重置为当前位置,然后继续执行,在这里可以动态修改剩余这段动画运行时的 Timing 和 Duration。withTimingParameters = nil时,以原来的 Timing 运行,这里以springTiming继续剩下的动画;动画的剩余运行时间为durationFactor * duration,durationFactor = 0时,运行时间依然为原来的duration。因此,

animator.continueAnimation(withTimingParameters:nil, durationFactor: 0)

相当于执行animator.startAnimation()来继续动画。

continueAnimation(withTimingParameters:durationFactor:)结束后,animator 的 Timing 依然是初始化时的 Timing,修改只是暂时的;不过durationFactor会修改 animator 原来的的duration(规则未知,每次调用这个方法都会修改,durationFactor = 0不会修改),从而影响后面添加的动画的运行时间,这是个奇怪的设计。

小结

上面的演示主要偏向于突出UIViewPropertyAnimator在交互方面的特性,它也完全可以当作 UIView Animation 一样使用,也可以混合这两种风格,我在ControlPanelAnimation中演示了多种风格实现上面的交互动画。不过即使假设实现逆转动画时的各种瑕疵是实现上的 Bug,在让普通的动画实现交互时,UIViewPropertyAnimator相对于 UIView Animation 并不具备优势:相比上篇中使用 UIView Animation 时的简单,UIViewPropertyAnimator引入的交互状态和解决不同转换问题时看似灵活的搭配选择,都显得太复杂了。

不过,使用UIViewPropertyAnimator实现转场动画在非交互与交互状态之间的自由切换是非常方便的,而且还能大幅精简当前复杂的转场协议体系,这得益于其封装的交互功能解决了最困难的部分,具体可查看「iOS 视图控制器转场详解」。

参考

WWDC 2016 Session 216: Advances in UIKit Animations and Transitions:https://developer.apple.com/videos/play/wwdc2016/216/

iOS 视图控制器转场详解:https://github.com/seedante/iOS-Note/wiki/ViewController-Transition

感谢徐川对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ,@丁晓昀),微信(微信号:InfoQChina)关注我们。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容