【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:313687

评论

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

极限编程技术实践

Teobler

敏捷 敏捷开发 TDD 重构 极限编程

Git学习游戏化,从Learn Git Branching 开始

程序老王

git 学习 学习方法 git 学习

一线互联网大厂面经分享:阿里三面+头条四面+腾讯二面+美团四面

Java架构之路

Java 程序员 架构 面试 编程语言

程序员之禅(一)

每天读本书

读书笔记

EEPROM CAT24CXX实现分页读、写数据

不脱发的程序猿

28天写作 CAT24C08 EEPROM 嵌入式软件 单片机

Serverless 如何在阿里巴巴实现规模化落地?

阿里巴巴云原生

阿里巴巴 Serverless 容器 微服务 云原生

从0到1建立软件测试质量体系

程序员阿沐

软件测试 测试工程师 质量保证

浅谈基于ARP协议的网络攻击

行者AI

网络安全

LeetCode题解:123. 买卖股票的最佳时机 III,动态规划,JavaScript,详细注释

Lee Chen

算法 大前端 LeetCode

算力挖矿系统开发|算力挖矿软件APP开发

系统开发

四面美团开发岗,成功斩获offer,分享个人面经

Java架构之路

Java 程序员 架构 面试 编程语言

Vim,人类史上最好用的文本编辑器

沉默王二

vim 开发工具 vim教程

智慧党建管理系统,智慧组工平台开发方案

13530558032

【LeetCode】区域和检索 - 数组不可变Java题解

Albert

算法 LeetCode 28天写作

887页Java面试“成神”手册,已助朋友狂砍9个一二线大厂Offer

Java架构追梦

Java 阿里巴巴 架构 面试 金三银四

七种分布式事务的解决方案,一次讲给你听

moon聊技术

技术案例 | 云原生微服务落地难?百度自用CRM这样做

百度开发者中心

微服务 CRM #百度智能云#

OS命令--shell中数组的操作

cloudcoder

数组 Shell 循环引用

Kubernetes 稳定性保障手册 -- 极简版

阿里巴巴云原生

云计算 容器 开发者 云原生 k8s

2021备战金三银四血拼一波算法:字节+百度+美团+网易+拼夕夕+腾讯+滴滴

比伯

Java 编程 程序员 架构 面试

区块链农产品溯源平台,农产品区块链防伪

13530558032

2021最新京东、字节跳动「3面面经」盘点大厂后端面试高频题

Java架构之路

Java 程序员 架构 面试 编程语言

山东青岛推进平安小区建设!源中瑞智慧社区平台解决方案

源中瑞-龙先生

解决方案 山东 源中瑞 青岛 智慧社区

程序员成长第十七篇:项目转测

石云升

项目管理 程序员 28天写作 3月日更

Pgbouncer最佳实践:系列一

PostgreSQLChina

数据库 postgresql 软件 开源社区

#集赞送好礼#百度大脑AI开放平台的2020年

百度大脑

第四章作业(二)

LouisN

技术解析 | Doris Compaction机制解析

百度开发者中心

百度 apache doris

接口测试--apipost中cookie管理器的使用

测试人生路

接口 Cookie

国产芯片WiFi物联网智能插座—电耗采集功能设计

不脱发的程序猿

28天写作 国产芯片 电耗检测 电压电流 华大MCU

从0到1建立数据分析指标体系底层逻辑

小飞象@木木自由

数据分析 数据指标 数据分析体系

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