Wave

Wave 是一个基于弹簧的动画引擎,适用于 iOS、iPadOS 和 macOS。它可以轻松创建流畅、交互式和可中断的动画,带来绝佳的体验。

Wave 没有外部依赖项,并且可以轻松地集成到现有的基于 UIKit、SwiftUI 或 AppKit 的项目和应用程序中。

Wave 的核心特性是所有动画都是可重定向的,这意味着您可以在动画进行过程中更改动画的目标值,动画会平滑地重定向到新的值。

理解重定向

请看这些 iOS 画中画功能的演示。左侧屏幕使用标准的 UIKit 动画创建,右侧屏幕使用 Wave 创建。

虽然两者都是“可中断的”,但基于 Wave 的实现能更好地处理中断,并流畅地弧线过渡到新的目标。相比之下,UIKit 动画感觉生硬且顿挫。

从本质上讲,重定向是在目标改变时仍保持动画速度的过程,而 Wave 可以自动实现这一点。

Demo

安装

将 Wave 添加到您应用程序的 Package.swift 文件中,或在 Xcode 中选择 File -> Add Packages

.package(url: "https://github.com/jtrivedi/Wave")

如果您克隆了仓库,您可以运行示例应用程序,其中包含一些交互式演示,以帮助您理解 Wave 的功能。

注意:要在 ProMotion 设备上启用高帧率动画(即 120 fps 动画),您需要在您的 Info.plist 文件中添加一个键值对。将键 CADisableMinimumFrameDuration 设置为 true。如果没有此条目,动画将被限制为 60 fps。

文档

有一个完整的 Wave 文档站点,提供完整的 API 和使用文档。

入门

根据您的需求,您可以通过两种方式与 Wave 交互:基于块的动画和基于属性的动画。

基于块的动画

最简单的入门方式是使用 Wave 基于块的 API,这些 API 类似于 UIView.animateWithDuration() API。

此 API 允许您动画化几个常见的 UIView 和 CALayer 属性,例如 framecenterscalebackgroundColor 等。

对于这些受支持的属性,Wave 将在底层创建、管理和执行所需的弹簧动画。

例如,将上述 PiP 视图动画化到其最终目的地非常简单。

if panGestureRecognizer.state == .ended {

    // Create a spring with some bounciness. `response` affects the animation's duration.
    let animatedSpring = Spring(dampingRatio: 0.68, response: 0.80)

    // Get the gesture's lift-off velocity, and pass it into the Wave animation.
    let gestureVelocity = panGestureRecognizer.velocity(in: view)

    Wave.animate(withSpring: animatedSpring, gestureVelocity: gestureVelocity) {
        // Update animatable properties on the view's `animator` property, _not_ the view itself.
        pipView.animator.center = pipViewDestination     // Some target CGPoint that you calculate.
        pipView.animator.scale = CGPoint(x: 1.1, y: 1.1)
    }
}

请注意,在任何时候,您都可以将视图的 center 属性重定向到其他位置,它都会平滑地进行动画。

支持的可动画属性

基于块的 API 当前支持动画化以下属性。对于其他属性,您可以使用下面的基于属性的动画 API。

即将推出的属性

基于属性的动画

虽然基于块的 API 通常最方便,但您可能希望动画化基于块的 API 尚不支持的内容(例如 rotation)。或者,您可能需要获取中间弹簧值并自行驱动动画的灵活性(例如进度值)。

例如,要绘制 PiP 演示的橙色路径,我们需要知道从视图的初始中心到其目标中心之间每个 CGPoint 的值。

// When the gesture ends, create a `CGPoint` animator from the PiP view's initial center, to its target.
// The `valueChanged` callback provides the intermediate locations of the callback, allowing us to draw the path.

let positionAnimator = SpringAnimator<CGPoint>(spring: animatedSpring)
positionAnimator.value = pipView.center       // The presentation value
positionAnimator.target = pipViewDestination  // The target value
positionAnimator.velocity = gestureVelocity

positionAnimator.valueChanged = { [weak self] location in
    self?.drawPathPoint(at: location)
}

positionAnimator.start()
完成代码块

基于块和基于属性的 API 都支持完成代码块。如果动画完全完成,则完成代码块的 finished 标志将为 true。

但是,如果动画的目标在进行过程中被更改(“重定向”),则 finished 将为 false,而 retargeted 将为 true。

Wave.animate(withSpring: Spring.defaultAnimated) {
    myView.animator.backgroundColor = .systemBlue
} completion: { finished, retargeted in
    print(finished, retargeted)
}

示例代码

探索提供的示例应用程序是开始使用 Wave 的绝佳方式。

只需打开 Wave-Sample Xcode 项目并点击“Run”。画中画演示的完整源代码也在此处提供!

致谢

特别感谢 Ben Oztalay,感谢他帮助设计 Wave 的底层物理原理!