MotionMachine 提供了一个模块化、强大且通用的平台,用于操作各种值,无论是 UI 元素的动画还是您自己的类中的属性值插值。它提供了合理的默认功能,抽象掉了大部分繁重的工作,让您可以专注于自己的工作。虽然它是类型无关的,但 MotionMachine 确实开箱即用地支持大多数主要的 Apple 平台类型作为对象状态,并提供了语法糖来轻松地操作它们。但它也很容易深入并根据您的自身需求进行修改,无论是自定义 motion 类、支持自定义值类型还是新的缓动方程。
CGPath
(甚至是路径的一部分)为 CGPoint
制作动画。如果您是从以前版本的 MotionMachine 升级而来,请查看 3.0 迁移指南,了解重大更改。
还可以查看 示例项目,以查看 UIKit 和 SwiftUI 中的 MotionMachine 类示例,或深入研究源代码文档。
这个复杂的动画是使用下面的代码示例创建的。这些 Motion
类为圆形视图的 NSLayoutConstraints 以及它们的其中一个 backgroundColor
属性制作动画。MotionGroup
对象用于同步这四个 Motion
对象并反转它们的运动。
let group = MotionGroup(options: [.reverses])
.add(Motion(target: circleViewXConstraint,
properties: [PropertyData(keyPath: \NSLayoutConstraint.constant, end: 200.0)],
duration: 1.0,
easing: EasingQuartic.easeInOut()))
.add(Motion(target: circleViewYConstraint,
properties: [PropertyData(keyPath: \NSLayoutConstraint.constant, end: 250.0)],
duration: 1.4,
easing: EasingElastic.easeInOut()))
.add(Motion(target: circleView,
states: MotionState(keyPath: \UIView.backgroundColor[default: .black], end: .systemBlue),
duration: 1.2,
easing: EasingQuartic.easeInOut()))
let circle2Motion = secondCircleXConstraint,
properties: [PropertyData(keyPath: \NSLayoutConstraint.constant, end: 300.0)],
duration: 1.2,
easing: EasingQuadratic.easeInOut())
.reverses(withEasing: EasingQuartic.easeInOut())
circle2Motion.reverseEasing = EasingQuartic.easeInOut()
group.add(circle2Motion)
group.start()
MotionMachine 中包含的所有 motion 类都采用了 Moveable
协议,这使它们能够无缝地协同工作。通过使用 MotionGroup
和 MotionSequence
集合类来控制多个 motion 对象(甚至嵌套多个层),您可以轻松创建复杂的动画。
Motion
使用 Swift 的 KeyPath 来定位对象的特定属性,并通过缓动方程在一段时间内转换它们的值。虽然我们可以通过 PropertyData
对象直接提供这些转换指令,但在插值多个对象值时,这可能会变得笨拙。为了缓解这种情况,Motion
也接受 MotionState
对象,这些对象提供了对象最终状态的表示。在本示例中,我们为 transform 提供了 CGAffineTransform
对象,为目标视图的 backgroundColor 提供了 UIColor
对象。Motion
将自动从这些状态创建 PropertyData
对象。
let transformState = MotionState(keyPath: \UIView.transform, end: circle.transform.scaledBy(x: 1.5, y: 1.5))
let colorState = MotionState(keyPath: \UIView.backgroundColor[default: .black], end: .systemBlue)
// The `states` parameter here is a parameter pack of `MotionState` objects which have unique generic types. Pass them in as you would a normal variadic parameter.
motion = Motion(target: circleView,
states: transformState, colorState,
duration: 2.0,
easing: EasingBack.easeInOut(overshoot: 0.5))
.reverses()
.start()
MotionGroup
是一个 MoveableCollection
类,它管理一组 Moveable
对象,并行控制它们的运动。它对于控制和同步多个 Moveable
对象非常方便。MotionGroup
甚至可以控制其他 MoveableCollection
对象。在下面的示例中,我们告诉 MotionGroup 在执行同步操作的同时反转和同步其子 motion。这意味着它将在正向运动完成后暂停所有 motion,然后才反转它们。在这种情况下,水平运动会暂停,同时等待修改第二个圆圈 backgroundColor 的 Motion 完成其 3 秒的持续时间。
// the MotionGroup will wait for all child motions to finish moving forward before starting their reverse motions
group = MotionGroup().reverses(syncsChildMotions: true)
// move first circle horizontally
let horizontal1 = Motion(target: constraints["x1"]!,
properties: [PropertyData(keyPath: \NSLayoutConstraint.constant, end: 250.0)],
duration: 1.5,
easing: EasingSine.easeOut())
.reverses()
group.add(horizontal1)
// reverse and repeat horizontal movement of second circle once, with a subtle overshoot easing
let horizontal2 = Motion(target: constraints["x2"]!,
properties: [PropertyData(keyPath: \NSLayoutConstraint.constant, end: 250.0)],
duration: 1.0,
easing: EasingBack.easeOut(overshoot: 0.12))
.reverses()
group.add(horizontal2)
// Change the backgroundColor of the second circle. The "default" subscript in the keyPath is due to UIView's `backgroundColor` property being an optional.
let color = Motion(target: circles[1],
states: MotionState(keyPath: \UIView.backgroundColor[default: .black], end: .systemBlue),
duration: 3.0,
easing: EasingQuadratic.easeInOut())
group.add(color)
.start()
MotionSequence
是一个 MoveableCollection
类,它按顺序移动 Moveable
对象的集合,甚至包括其他 MoveableCollection
对象。MotionSequence
提供了一种强大而简单的方法,可以将对象属性的值转换链接在一起,以进行关键帧动画或创建多个对象的复杂而流畅的复合动画。
// Create a reversing MotionSequence with its reversingMode set to contiguous to create a fluid animation from its child motions. We could make these one Motion with multiple states, but we want to use different easing equations and durations on the view properties.
sequence = MotionSequence().reverses(.contiguous)
// set up motions for each circle and add them to the MotionSequence
for circle in circles {
// motion to animate a UIView's origin
let down = Motion(target: circle,
properties: [PropertyData(keyPath: \UIView.frame.origin.y, end: 60.0)],
duration: 0.4,
easing: EasingQuartic.easeInOut())
// motion to change background color of circle
let color = Motion(target: circle,
states: MotionState(keyPath: \UIView.backgroundColor[default: .black], end: .systemBlue),
duration: 0.3,
easing: EasingQuadratic.easeInOut())
// wrap the Motions in a MotionGroup and set it to reverse
let group = MotionGroup(motions: [down, color]).reverses(syncsChildMotions: true)
// add group to the MotionSequence
sequence.add(group)
}
sequence.start()
您可以通过将 MotionMachine 添加为 Swift Package 依赖项来将其添加到 Xcode 项目。
.product(name: "MotionMachine", package: "MotionMachine")
MotionMachine 目前需要
结构体不能用作 KeyPath 的顶层,但您可以将它们用作顶层对象的后代。
Key Path 中的可选值是受支持的,但是当声明路径时,您必须通过下标为它们提供默认值,使用格式 \Object.someOptional[default: <some default value>]
。
MotionMachine 由 Brett Walker 创建。它大致基于作者的 Objective-C 库 PMTween。
MotionMachine 在 MIT 许可证下获得许可。有关详细信息,请参阅 LICENSE。
如果您在项目中使用 MotionMachine,我很想知道!