大多数浏览器和
Developer App 均支持流媒体播放。
-
用弹簧制作动画
了解如何通过动画为你的 App 带来活力!我们将向你展示如何利用弹簧创建令人惊叹的动画,并帮助你学习如何在 App 中实际应用弹簧。
章节
- 1:36 - Why springs
- 8:33 - How springs work
- 17:29 - How to use springs
资源
相关视频
WWDC23
-
下载
♪ ♪
Jacob:大家好 我是 Jacob 我们构建的 UI 变得越来越动态 到处都有变化和动作 用户喜欢这样 这使得界面更具生气 让用户更易理解发生了什么 并且与 UI 的交互带来愉悦感
这些动态交互 需要多种元素共同合作 包括转场 我们从一个场景切换到另一个场景; 以及手势 我们直接与设备交互; 最后还有动画 屏幕上的物体移动、 增长或改变可视属性 这些元素共同协作 使 UI 流畅且具有交互性 今天 我们将深入研究 如何构建出色的动画 动画之间的差异可能很细微 但当动画刚好合适时 用户会有所感知 因此 我想与你分享 如何使 App 中的动画 优雅且自然 我们将探讨一个功能强大、 用途多样、趣味横生的工具: 弹簧! 我们将首先讨论 为什么弹簧很适合用于动画 然后深入了解弹簧的工作原理 最后 我们将讨论 如何在 App 中使用弹簧 那么 为什么弹簧在动画中如此有用呢? 为了回答这个问题 我们需要 先讨论是什么造就了出色的动画 让我们看一个 上下文中的几个动画示例: 一个简单的开关 我们来观察这个开关动画
我们使用动画有几个原因 其中最重要的原因之一 是它们给我们带来了出色的连续感 如果一个物体始于一个位置 然后突然在另一个位置出现 会感觉很突兀 有时可能令人困惑 如果我们看到物体 从一个位置移动到另一个位置 那么这种感觉就会更自然
但连续感不仅仅是指 位置方面的连续 如果一个物体的速度 突然改变 也会感觉不自然 例如 在这里 开关中的旋钮在开始和结束时 有速度上的跳跃 感觉就不对 因此 我们的目标之一是让动画 有连续顺畅的位置和速度 我们来看一下几种动画类型 并观察它们在该要求下的表现 虽然我们的开关 是一个实用的完整动画示例 但我想只关注旋钮的运动 以便更简单地看到 单个动画中发生的情况 让我们先看看缓入缓出动画 这是一种贝塞尔曲线动画类型 意味着它的运动由曲线 和持续时间组合而成 从这个物体的运动来看 没有感觉到任何的突然跳跃 为了确保如此 我们还可以查看该动画的运动图表
我们将会查看多个这样的图表 让我们来探讨一下它们的展示方式 水平轴表示时间 底线显示动画的初始位置 顶线显示目标位置 我们将重复播放动画 物体将会按照 指定的曲线进行反复的动画过程 现在让我们重新开始动画 注意图表中的曲线没有任何跳跃 这意味着它的位置是连续的 如果我们更新图表来显示速度 我们还可以确认速度也没有突变 所以速度也是连续的
如果我们看线性动画的运动 会发现在动画开始和结束时 有一个急转弯和速度的跳跃 线性动画作为专用工具 在某些场景下可能很有用 比如重复旋转指示器 但在其他情况下要慎重使用 尤其是在需要移动的情况下 因为它表现出非物理动画效果 通常会让人感觉不合适
接下来 让我们检查弹簧动画的连续性 正如我们所期望的 它的位置和速度也是连续的 到目前为止 缓入缓出 和弹簧动画是我们的最佳选择 但我们只观察了动画从静止位置 开始的情况 让我们看一下 如果我们将手势 与动画结合使用会发生什么 将我们的 可移动旋钮移到 iPad 上 并用手指拖动它
我们拖动它 将其放在 这两个位置中的任何一个 我们也可以在手势 进行过程中将手势停在中间位置 然后将旋钮向一侧扔出
使用缓入缓出动画时 它确实会动画到最终位置 但它的运动 在手势结束时会突然停止
这种动画只是一个预先指定的曲线 所以无法表示初始速度 如果我们允许旋钮在二维空间中的 任意位置拖动 情况会更糟
我们来再试一次 但这次使用弹簧动画 弹簧可以开始于任何初始速度 因此我们得到了一个自然的感觉 动画可以从手势结束的位置开始
这也非常适用于二维拖动
在 SwiftUI 中 现在会自动跟踪 手势在更改属性时的速度 所以你不需要进行任何额外操作 便能轻松获得所需的动画效果 因此 弹簧动画 是唯一保持连续性的动画类型 适用于静态情况 和带有初始速度的情况 我们可以考虑弹簧的 另一个优点是其运动的形状
当你听到弹簧的运动时 你可能会想到类似于这样的动画
但是 弹簧动画 并不仅意味着反弹动画 的确 弹簧可以有反弹效果 这也是一个出色的工具 但这不是我们使用弹簧的主要原因 稍后 我们将查看何时适合 使用带反弹的弹簧 但没有反弹的弹簧也很好用! 这些非反弹型的弹簧在 iOS 的 各种动画中都得到广泛应用 因此 如果不只关注反弹效果 那么弹簧的运动有什么优点呢? 让我们再次来看看 我们简单的弹簧动画 并注意动画是如何结束的 它非常缓慢地逐渐停止 当物体运动突然结束时 并没有一个突然停止的瞬间 这更像是我们期望看到的 物体移动和停止的样子 这个版本之所以 给我们更自然的感觉 是有原因的 弹簧动画基于物理世界中 连接在弹簧上的物体的行为 因此当我们看到它运动时 会给我们的视觉 带来更加自然和可信的感觉 现在你可能听说过 使用弹簧进行动画的不同属性 可能在不同时间结束 这是正确的 如果你习惯了使用 时序曲线动画 这可能会感到奇怪 难道我们不希望 所有动画都同时开始和结束吗? 并不是这样 我们希望我们的动画能够让人感觉 像是在我们熟悉的 物理世界中物体的运动一样 通常 这些动画因受到摩擦的影响 而在各自的时间内启动和停止 因此这些时间通常不会完全一致 事实上 进行多属性动画时 有时 我们需更进一步调整控制动画效果 这是一个 App 在 iOS 上启动时的动画 乍一看 它似乎只是一个统一的动画 但如果我们放慢动画 你会看到不同的弹簧、 不同的开始时间和结束时间 共同形成了一种 令人难以置信且感觉自然的动画
所以现在我们知道 为什么弹簧是制作动画的绝佳工具 让我们更仔细地了解一下 弹簧的工作原理 以及如何最好地使用它们 当我们使用弹簧动画时 我们正在模拟 物体连接到弹簧的运动 如果我们回顾物理学 这种运动由3个属性来定义: 物体的质量、 弹簧的刚度 和系统的阻尼 阻尼是物体受其周围环境摩擦 影响程度的度量 然后 我们使用动画的初始位置 作为物体的初始位置 并使用动画的目标位置 来定义弹簧的静止位置 即物体将被拉向的位置 然后我们释放物体以开启动画 我们用来定义弹簧系统的属性 决定了所发生的运动类型 而更改这些属性 会改变产生的动画效果
因此 在创建弹簧动画时 你可以使用 这些质量、刚度和阻尼的相同属性 来配置要使用的弹簧类型 虽然这些属性 是模拟物理系统的自然方式 但对于定义弹簧动画来说 并不是非常直观 在这里并没有真实的具有质量的 物体或者具有刚度的弹簧 而且要想随意编造这些值 来改变动画曲线并不容易 因此 我们一直在改进 一种新的弹簧配置方式 该方式更易于理解和使用 它仅使用两个参数: 持续时间和反弹 这些参数可以实现你预期的效果 增加持续时间会使动画时间更长 增加反弹会使动画反弹 我们正在 Apple 的设计和工程工作中 全面采用这些参数 所以 我们所有支持 弹簧效果的框架都会使用它们
当你调整这些弹簧参数时 你会看到曲线中 出现不同类型的形状 当弹性值大于 0 时 我们得到一个有弹性的 弹簧动画 它会超过目标位置 当弹性值为 0 时 我们得到一个平滑的曲线 有一个较长的尾部 逐渐接近目标位置 还有另一种类型的弹簧 该弹簧不太常见 但当弹性值为负值时 你可以得到一个弹簧动画 它也有一个长尾逐渐接近目标位置 但比弹性值 为 0 的情况要平缓一些 在有关弹簧的物理学中 这些分别称为欠阻尼、 临界阻尼和过阻尼弹簧 但我更喜欢称它们为 有弹性、平滑和平缓 你可能已经注意到 这些弹性值是以百分比表示的 所以有弹性的弹簧 弹性值可达 100% 而平缓的弹簧 弹性值在 0 和 -100% 之间 现在我想更详细地 解释一下这些弹簧 弹簧有时看起来有点让人生畏 它们的运动似乎难以理解 但如果我们分解其中的运动过程 实际上只是 几个简单的元素组合而成 对我来说 了解这些曲线背后的数学原理 有助于让弹簧动画更易于理解 所以我也想与你分享这方面的知识 但是 如果数学让你头晕 别担心 这是完全可选的 我们为你实现所有这些数学计算
让我们先从一个有弹性的曲线开始 你可能会注意到 该弹簧的过冲行为 类似一种 更复杂的正弦或余弦波振荡 如果我们将弹性值提高到 100% 你会发现 它现在为余弦波的动画效果 只是来回振荡
从物理学的角度来看 这是一种没有阻力作用的弹簧 它将永远振荡而不会减速 并且永远不会真正到达目标位置 正如你所预期的那样 该动画的数学原理相当简单: 它只是一个余弦曲线 而时间除以持续时间 来控制动画的速度 所以对于这个弹性值 持续时间 恰好对应曲线的周期 随着弹性值的减小 在物理上 相当于给弹簧增加摩擦或阻尼 这会使它在运动过程中减速 动画的振荡效果仍然存在 事实上 之前的余弦曲线还在 如果我们将其 叠加在一起绘制 这将更加清楚 这与之前的方程式相同 只是具有不同的常数 并稍微水平偏移了一点 这解释了曲线中的反弹特性 但显然我们还需要另一部分 在原始曲线中 振荡的幅度 会随着时间推移减小或衰减 这就是缺失的部分 这个附加曲线是一条指数衰减曲线 它是我们运动的最后一个部分 这部分让我们 逐渐感觉到物体正在缓慢停止
所以 看起来复杂的曲线实际上 只是这两个组成部分的乘积 被称为阻尼余弦或正弦波 这还算是令人满意的 但是 如果你仔细观察图表 可能会发现一些奇怪的地方: 为什么我们的余弦曲线 在开始时会出现凹陷? 这与我们之前讨论的某样东西有关: 保持速度 记得在这种基本情况下 我们需要 保持起始时的速度为 0 因此 两个组成部分曲线的乘积速度 在起始处应该保持在 0 附近 但请注意 我们的衰减曲线 在起始处有一个向上的斜率 如果我们 从余弦曲线的平坦状态开始 我们的初始速度也会朝上 所以我们的余弦曲线 必须从向下的方向开始 以与衰减相抵消 使我们的起点平坦 这就是弹簧动画如何通过在 余弦曲线中进行不同的平移和缩放 来匹配任意初始速度 从而实现我们所需的正确起始效果
正如我们之前所讨论的 该初始速度可以来自手势结束 并切换到动画时的速度 还有另一种情况 可以产生这个初始速度 让我们在 iPad 上进行观察 在这里 我们可以轻点以移动我们的旋钮 并显示其目标位置的淡色图像 我们还将使用较慢的弹簧 以便更容易跟踪发生的情况 有时 在动画尚未完成时 会开始新的动画 该动画会更改为新的目标值 当发生这种情况时 弹簧动画会使用 它重新定位时的速度作为初始速度 朝向新的目标位置 同样的速度保持 使得这种中断感觉平滑和自然
这就是速度保持 和有弹性的弹簧动画的工作原理 现在如果我们减小弹簧的弹性 振荡的幅度 会变得越来越大 相距甚远 直到弹性减到 0 为止 振荡就完全消失了 我们就只有一条 从上而下、逐渐远离的直线 并且这条直线 会受到衰减效果的影响 所以 这些公式变得更加简单 我们只需要一个基本的直线方程 然后将其乘以相同的指数 得到我们的最终曲线
平缓的曲线 也就是弹性值为负值的弹簧 工作方式非常类似 但是使用两个指数函数相加 而不是直线 这种类型的弹簧较少见 但事实上 它仅由指数衰减表示 使其适用于模拟速度衰减 就像在滚动视图中发生的情况一样
当使用弹簧 进行动画时 你可能会想知道 弹簧动画需要 多长时间才能真正完成? 正如我们所见 这个问题有些微妙 弹簧的指数衰减 意味着事实上它会一直持续运动 只是运动幅度越来越小 当然 我们不希望 弹簧动画一直持续下去 所以当它不再对 UI 产生明显的变化时 我们需要选择一个时间来移除它 弹簧动画在完成后 足以从界面中移除的时间长度 被称为“稳定时长” 这个“稳定时长”不同于 配置弹簧的时长参数 “稳定时长” 取决于许多不同的因素 因此可能有点不可预测 但时长参数是感知持续时间 即使弹簧的其他参数发生变化 它也被选择为可预测并且不会改变 由于其不可预测的性质 你不应该等待“稳定时长” 以进行面向用户的更改 如果你希望在弹簧动画 几乎完成时进行 UI 的变化 你可以使用 SwiftUI 中 新的完成处理程序支持 它使用感知持续时间 而不是“稳定时长” 现在我们已经 了解了弹簧的工作原理 我们来讨论一下如何在代码中使用 由于弹簧 是用于制作动画的绝佳工具 我们现在在 SwiftUI 中 将其作为默认的动画方式 所以你只需要调用 withAnimation 就可以开始使用弹簧 我们还简化了 显式使用弹簧动画的方式 我们根据 iOS 中使用的弹簧值 内置了一些弹簧预设值 如果你不确定要使用什么弹簧参数 这些预设值是 获得一个出色动画的好方法
当你需要一个动画时 你可以直接 在代码中使用这些预设 但是使用弹簧动画的一个重要部分是 对它进行调整 以适应你需要的确切情况 所以这些预设值 也可以作为可调整的起点 你可以获取一个预设值 并指定其持续时间应该不同 或者通过指定相对的额外弹性量 来增加或减少弹簧的振动 这些预设是在你的 App 中 开始使用弹簧动画的绝佳方式 但如果你想进一步探索 你还可以使用 .spring 动画 创建完全自定义的弹簧动画 这让你可以完全指定弹簧的持续时间 和弹性 这些弹性值范围 从 -1.0 到 1.0 你还可以使用这些参数 在 UIKit 和 Core Animation 中创建弹簧动画
如果你想进一步深入使用弹簧动画 还有一种新的弹簧工具可供你使用 我们在 SwiftUI 中 添加了弹簧模型类型 让你可以创建 表示弹簧的模型 包括其参数 这个功能允许你以编程的方式 在不同的 参数指定方式之间进行参数转换 你也可以创建一个带有一组 参数的弹簧模型 比如质量、 刚度和阻尼 然后直接将其用作弹簧动画 而如果你真的想自行进行转换 以下三个方程式可以帮你 将弹性和持续时间值转换成质量、 刚度和阻尼 除了参数转换 你还可以使用弹簧模型 构建自定义的高级弹簧行为 你可以调用弹簧上的方法 自行获得内置的弹簧求值 数学计算 例如 你可以调用 value 来获得弹簧的位置 你只需要提供一个目标值 也就是弹簧动画要移动到的位置 以及你希望在 哪个时间点对其进行评估 你还可以使用 velocity 方法 进行相同的输入 以评估弹簧随时间推移的速度 这让你可以轻松地 在你的代码中使用弹簧 这对于模拟 或获取图表数值非常有用 就像本讲座所展示的那样 你甚至可以用它 来构建你自己的自定义动画 只需调用弹簧模型 你就可以修改输入或输出 来对弹簧动画进行定制 查看“探索 SwiftUI 动画” 以了解制作自定义动画的详细内容 最后 我想探讨一下 如何为你的弹簧选择弹簧参数 为了选择一个适合你的动画的值 通常最好从找到一个提供你 心仪节奏的持续时间值开始 一旦你决定了持续时间 你可以开始调整弹性大小 选择你想要这个动画 呈现的特性和感觉 你会发现不同的弹性值有质的不同 弹性值为 0 时 会感觉像是平滑的渐变 较小的弹性值 如约为 15% 虽弹性不强 但末尾的效果会稍显轻快 而较大的弹性值 如约为 30% 你会感受到明显的弹性效果 如果再增加 弹簧的弹性会相当大 但是注意 不要使用高于约 0.4 的值 因为对 UI 元素来说 它们可能会产生过度夸张的效果 那么到底应该使用哪个弹性值呢? 当你不确定时 可以使用弹性值为 0 的弹簧 这也是如果你没有 指定弹性值时获得的效果 这为你提供了一个 功能最全的通用弹簧 然后 如果你想让 你的动画感觉更有趣 可以开始增加一些弹性 当你希望动画更有实体感时 比如用在手势结束时 使用弹性也是很合适的选择 你需要时刻记得保持一致性 思考你的 App 有什么样的特性 是严肃还是有趣? 应该感觉轻松还是快节奏? 这可以帮助你选择 与 UI 感觉一致的弹簧值 以上就是 关于使用弹簧动画的快速介绍 请记住 弹簧不需要弹性 就可以制作出色的动画 现在还有一组新的弹簧预设 可以帮助你开始创建弹簧动画 但如果你需使用获取高级的动画 你可使用持续时间和弹性来自定义 最重要的是 在看到弹簧的 所有独特优势之后 希望你对使用弹簧动画充满兴趣 使你的 App 在交互中更加流畅、愉悦 谢谢 ♪ ♪
-
-
18:00 - Spring Preset
withAnimation(.snappy) { // Changes }
-
18:15 - Spring Preset with Custom Duration
withAnimation(.snappy(duration: 0.4)) { // Changes }
-
18:21 - Spring Preset with Custom Bounce
withAnimation(.snappy(extraBounce: 0.1)) { // Changes }
-
18:37 - Custom Spring
withAnimation(.spring(duration: 0.6, bounce: 0.2)) { // Changes } // UIKit UIView.animate(duration: 0.6, bounce: 0.2) { // Changes } // Core Animation let animation = CASpringAnimation(perceptualDuration: 0.6, bounce: 0.2)
-
18:57 - Spring Model
let mySpring = Spring(duration: 0.5, bounce: 0.2) let (mass, stiffness, damping) = (mySpring.mass, mySpring.stiffness, mySpring.damping)
-
19:16 - Spring Model Animation
let otherSpring = Spring(mass: 1, stiffness: 100, damping: 10) withAnimation(.spring(otherSpring)) { // Changes }
-
19:26 - Spring Parameter Conversion
mass = 1 stiffness = (2π ÷ duration)^2 damping = 1 - 4π × bounce ÷ duration, bounce ≥ 0 4π ÷ (duration + 4π × bounce), bounce < 0
-
19:35 - Evaluating Spring Model
let mySpring = Spring(duration: 0.4, bounce: 0.2) let value = mySpring.value(target: 1, time: time) let velocity = mySpring.velocity(target: 1, time: time)
-
20:15 - Custom Spring Animation
func animate<V: VectorArithmetic>( value: V, time: Double, context: inout AnimationContext<V> ) -> V? { spring.value( target: value, initialVelocity: context.initialVelocity, time: effectiveTime(time: time, context: context)) }
-
20:34 - Spring with No Bounce
withAnimation(.spring(duration: 0.5)) { isActive.toggle() }
-
21:07 - Spring with Small Bounce
withAnimation(.spring(duration: 0.5, bounce: 0.15)) { isActive.toggle() }
-
21:14 - Spring with Large Bounce
withAnimation(.spring(duration: 0.5, bounce: 0.3)) { isActive.toggle() }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。