AI实践哪家强?来 AICon, 解锁技术前沿,探寻产业新机! 了解详情
写点什么

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

  • 2016-07-28
  • 本文字数:4429 字

    阅读完需:约 15 分钟

不久前结束的 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 类终于带来了期待已久的两点改进:

  1. 以向量CGVector(dx: CGFloat, dy: CGFloat)为单位的初始速度,在 iOS 10 之前的弹簧动画 API 里的速度都是数值,在位移动画里方向是沿着起点到终点的直线方向,速度为向量意味着合成的初始速度可以不沿着这个方向;速度分量为负时,以 X 轴方向分量dx为例,表示与目标方向在 X 轴的分量相反,而非是沿着 X 轴反方向;
  2. 完全版本的弹簧动画: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 手势),要解决三个转换问题,也是所有交互动画需要解决的问题:

  1. Animation to Gesture:动画过程中切入滑动控制,需要中止当前的动画并由手指来控制控制板的移动;
  2. Gesture to Animation:滑动结束后添加新的动画,并与当前的状态平滑衔接,这需要 Spring 动画;
  3. 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 * durationdurationFactor = 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 视图控制器转场详解」

参考

  1. WWDC 2016 Session 216: Advances in UIKit Animations and Transitions: https://developer.apple.com/videos/play/wwdc2016/216/
  2. iOS 视图控制器转场详解: https://github.com/seedante/iOS-Note/wiki/ViewController-Transition

感谢徐川对本文的审校。

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

2016-07-28 17:314222

评论

发布
暂无评论
发现更多内容

喜报 | Bonree ONE 2.0荣获信通院“2022IT新治理年度明星产品”

博睿数据

可观测性 博睿数据 荣誉 ONE平台

直播继续!华为云Solution as Code一键高效上云,解决方案开箱即用

科技怪授

华为云

华为云12·12直播EI专场即将开始,满足电商行业全场景搜索需求

科技怪授

华为云

浪潮 KaiwuDB 陈磊:布局数字能源,创新助力 “双碳”

KaiwuDB

关于K8s集群环境工作组隔离配置多集群切换的一些笔记

山河已无恙

k8s管理 K8s 多集群管理 12月月更

哪篇论文宣布了 HTAP 数据库的诞生?解读《A Common Database Approach for OLTP and OLAP Using an In-Memory Column DataBase》

StoneDB

MySQL HTAP 数据库· StoneDB 12 月 PK 榜

面了40+岁的大叔,没有录用,并不是因为年龄

产品运营心经

工作经历 面试‘ 职场发展 大龄求职

AlibabaP8,耗时182天肝出来1015页分布式全栈手册

程序知音

Java 分布式 后端 java架构

什么样的web前端培训靠谱?

小谷哥

终于被我发现了这个推特视频下载的方法!超级简单!支持苹果安卓双系统!

frank

推特视频下载

来聊一聊 ElasticSearch 最新版的 Java 客户端

江南一点雨

Java elasticsearch springboot ES

骨灰级精品,京东百万架构师亲码的MySQL内部笔记太硬核了

小小怪下士

Java MySQL 程序员

系统的混乱并非业务本身之复杂,我们并不擅长处理『简单』

阿里技术

软件工程 复杂度

cdr2023断网离线激活下载教程

茶色酒

cdr2023

聚焦技术,锐意创新,GaussDB给世界一个更优选择

华为云开发者联盟

数据库 后端 华为云 12 月 PK 榜

PreSTU:一个专门为场景文本理解而设计的简单预训练模型

华为云开发者联盟

人工智能 华为云 OCR 12 月 PK 榜

大数据培训出来就业前景如何

小谷哥

震惊,WSL2居然可以挂载USB

吴脑的键客

WSL2 usb

Sovit3D引擎快速构建智慧变电站三维可视化系统

2D3D前端可视化开发

物联网 智慧变电站 智能变电站 数字孪生变电站 变电站可视化

AngularJS进阶(四十)创建模块、服务

No Silver Bullet

服务 模块 AngularJS 12月月更

【计算讲谈社】第十五讲|云端即时渲染:下一代互联网的算力基座?

大咖说

数字人 云游戏

一文带你快速上手云日志服务

云计算 运维 日志管理

上海靠谱的前端培训机构有没有推荐

小谷哥

AngularJS进阶(四十一)AngularJS中使用Chart.js制折线图与饼图实例

No Silver Bullet

AngularJS 12月月更 Chart.js 折线图与饼图

几种数据库jar包获取方式

华为云开发者联盟

数据库 华为云 12 月 PK 榜 jar包

java自学好还是培训好?

小谷哥

guitar pro2023下载官方版app

茶色酒

Guitar Pro guitar pro2023

如何合并Excel文档

Geek_249eec

C# Excel VB.NET

AngularJS进阶(三十九)基于项目实战解析ng启动加载过程

No Silver Bullet

项目实战 AngularJS 12月月更 启动加载

云原生 AI 的资源调度和 AI 工作流引擎设计分享

Baidu AICLOUD

AI工程化 异构计算 云原生AI 百度百舸

大数据培训工作就业前景怎么样

小谷哥

iOS交互式动画详解(下):iOS 10 的新变化_移动_seedante_InfoQ精选文章