红帽白皮书新鲜出炉!点击获取,让你的云战略更胜一筹! 了解详情
写点什么

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:313657

评论

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

合约一键跟单软件开发,币安欧意交易所合约跟单API量化机器人搭建

V\TG【ch3nguang】

量化交易机器人开发 合约跟单 币安智能链

低代码助力加速构建应用程序

高端章鱼哥

低代码开发 应用程序 JNPF

身为程序员,你有哪些提高写代码效率的工具?

这我可不懂

Google 代码编写 AI编码

想解决技术债,你要先学会如何量化它

互联网工科生

程序员 敏捷开发 技术债 敏捷宣言

服务网格实施周期缩短 50%,丽迅物流基于阿里云 ACK 和 ASM 的云原生应用管理实践

阿里巴巴云原生

阿里云 云原生 服务网格 容器服务

AI 狂飙,云端 IDE 如何书写未来?TVP 吐槽大会邀您来论道

CODING DevOps

netty WebSocket客户端实践

FunTester

软件测试/测试开发丨Web自动化测试策略

测试人

Python 程序员 软件测试 自动化测试 测试开发

Apache IoTDB:更适合工业物联网场景的新型数据库,存、查、用不再是难题

Apache IoTDB

《揭秘软件开发文档:你的项目管理必备利器》

金陵老街

开发文档

快手StreamLake:构建大算力基础设施体系,应对视频和大模型挑战

Geek老T

大模型 AIGC

阿里云 X 森马 AIGC T 恤设计大赛开启!穿什么由你定,赢 Airpods,作品定制联名T恤

阿里巴巴云原生

阿里云 云原生 AIGC

思考 快与慢 在工作中的应用 1

程序员在修行

软博会,2024上海国际软件产品展览会|世亚软博会

AIOTE智博会

软件博览会 软件展会 软博会 世亚软博会 软件展览会

CAD迷你看图 for Mac(MiniCAD) v4.4.5中文特别版

mac

苹果mac Windows软件 CAD迷你看图 设计软件

K8s Operator 开发完整教程

baiyutang

云原生 k8s operator PaaS

非递归方式 实现 前中后序遍历二叉树

程序员在修行

递归

C++字符串详解

攻城狮Wayne

与信创国产化高度适配的低代码开发框架

力软低代码开发平台

BackupLoupe for Mac(数据还原备份工具)v3.9中文版

mac

苹果mac Windows软件 数据备份工具

低代码平台:IT开发的一种重要方式

树上有只程序猿

系统开发 低代码开发 JNPF 传统开发

如果你越来越冷漠

宇文辰皓

[ K8s Operator 开发完整教程-1 ] kind创建本地K8s集群

baiyutang

云原生 k8s operator PaaS

问题即答案:HarmonyOS NEXT,点燃应用生态星星之火

Geek_2d6073

英文阅读 The manager's path

程序员在修行

响应式编程——初识 Flux 和 Mono

emanjusaka

Java 响应式编程 后端

数字藏品交易平台App开发,数字藏品系统源码搭建

V\TG【ch3nguang】

数字藏品开发 数字藏品app

Elasticsearch最佳实践:如何保证你的数据安全

腾讯云大数据

Elastic Search

OSCS开源安全周报第 57 期:Smartbi windowUnloading限制绕过导致远程代码执行

墨菲安全

网络安全 安全漏洞 oscs

报名开启丨邀你一起探索云端 AI 新兴技术和发展模式

TRaaS

ARTS 打卡第二周

程序员在修行

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