原文链接 https://swiftui-lab.com/swiftui-animations-part1/
In this article we are going to dive into some advanced techniques to create SwiftUI animations. I will talk extensively about the Animatable protocol, its trusty companion animatableData, the powerful and often ignored GeometryEffect and the completely overlooked but almighty AnimatableModifier protocol.
在本文中,我们将深入探讨一些创建 SwiftUI 动画的高级技术。我将广泛讨论 Animatable 协议、其值得信赖的伴侣 animatableData、功能强大但经常被忽视的 GeometryEffect 以及完全被忽视但全能的 AnimatableModifier 协议。
These are all topics that have been totally ignored by the official documentation, and hardly ever mention in SwiftUI posts and articles. Still, they provide us with the tools to create some pretty nice animations.
这些都是官方文档完全忽略的主题,在 SwiftUI 的帖子和文章中几乎从未提及过。尽管如此,它们还是为我们提供了创建一些非常漂亮的动画的工具。
Before we get into these hidden gems, I want to make a very quick summary on some basic SwiftUI animation concepts. Just so that we are all in the same page. Please bear with me.
在我们进入这些隐藏的瑰宝之前,我想对一些基本的 SwiftUI 动画概念做一个非常快速的总结。只是为了让我们能有共同语言,请耐心听我说。
The complete sample code for this article can be found at:
本文的完整示例代码可在以下位置找到:
gist.github.com/swif...
Example8 requires images from an Asset catalog. Download it from here:
Example8 需要资产目录中的图像。从这里下载:
swiftui-lab.com/?smd...
Explicit vs. Implicit Animations 显式动画与隐式动画
There are two types of animations in SwiftUI. Explicit and Implicit. Implicit animations are the ones you specify with the .animation()
modifier. Whenever a animatable parameter is changed on a view, SwiftUI will animate from the old to the new value. Some animatable parameters are size, offset, color, scale, etc.
SwiftUI 中有两种类型的动画。显式和隐式。隐式动画是您使用 .animation()
修饰符指定的动画。每当视图上的可动画参数发生更改时,SwiftUI 都会从旧值动画到新值进行动画处理。一些可动画的参数包括大小、偏移量、颜色、比例等。
Explicit animations are those specified with a withAnimation { ... }
closure. Only those parameters that depend on a value changed inside the withAnimation closure will be animated. Let’s try some examples to illustrate:
显式动画是用 withAnimation { ... }
闭包指定的动画。只有那些依赖于 withAnimation 闭包中更改的值的参数才会被动画化。让我们尝试一些例子来说明:
The following example uses implicit animations to alter the size and opacity of an image:
以下示例使用隐式动画来更改图像的大小和不透明度:
Implicit Animation 隐式动画
struct Example1: View {
@State private var half = false
@State private var dim = false
var body: some View {
Image("tower")
.scaleEffect(half ? 0.5 : 1.0)
.opacity(dim ? 0.2 : 1.0)
.animation(.easeInOut(duration: 1.0))
.onTapGesture {
self.dim.toggle()
self.half.toggle()
}
}
}
The following example uses explicit animations. Here, both scale and opacity are changed, but only opacity will be animated, as it is the only parameter altered inside the withAnimation
closure:
以下示例使用显式动画。在这里,缩放和不透明度都会改变,但只有不透明度会被动画化,因为它是 withAnimation
闭包内唯一改变的参数:
Explicit Animation 显式动画
struct Example2: View {
@State private var half = false
@State private var dim = false
var body: some View {
Image("tower")
.scaleEffect(half ? 0.5 : 1.0)
.opacity(dim ? 0.5 : 1.0)
.onTapGesture {
self.half.toggle()
withAnimation(.easeInOut(duration: 1.0)) {
self.dim.toggle()
}
}
}
}
Note that you can create the same result, using implicit animations, by altering the order in which modifiers are placed:
请注意,您可以使用隐式动画,通过更改修饰符的放置顺序来创建相同的结果:
struct Example2: View {
@State private var half = false
@State private var dim = false
var body: some View {
Image("tower")
.opacity(dim ? 0.2 : 1.0)
.animation(.easeInOut(duration: 1.0))
.scaleEffect(half ? 0.5 : 1.0)
.onTapGesture {
self.dim.toggle()
self.half.toggle()
}
}
}
Should you ever need to disable an animation, you may use.animation(nil)
.
如果您需要禁用动画,您可以使用 .animation(nil)
.
How Do Animations Work 动画的工作原理
Behind all SwiftUI animations, there’s a protocol named Animatable
. We will go into the details later, but mainly, it involves having a computed property with a type that conforms to VectorArithmetic
. This makes it possible for the framework to interpolate values at will.
在所有 SwiftUI 动画的背后,都有一个名为 Animatable
.我们稍后会详细介绍,但主要是它涉及具有符合 VectorArithmetic
的类型的计算属性。这使得框架可以随意插值。
When animating a view, SwiftUI is really regenerating the view many times, and each time modifying the animating parameter. This way it goes progressively from the origin value to the final value.
在对视图进行动画处理时,SwiftUI 实际上是多次重新生成视图,并且每次都会修改动画参数。这样,它就从原始值逐步变为最终值。
Suppose we create a linear animation for the opacity of a view. We intend to go from 0.3 to 0.8. The framework will regenerate the view many times, altering the opacity by little increments. Since opacity is expressed as a Double
, and because Double
conforms to VectorArithmetic
, SwiftUI can interpolate the opacity values required. Somewhere in the framework’s code, there’s probably an algorithm like this:
假设我们为一个视图的不透明度创建一个线性动画。我们打算从 0.3 到 0.8。该框架将多次重新生成视图,以小幅度的增量来改变不透明度。由于不透明度是以 Double
表示,而且Double
遵守 VectorArithmetic
协议,SwiftUI 可以插值出所需的不透明度值。在框架代码的某个地方,可能有一个类似的算法:
let from:Double = 0.3
let to:Double = 0.8
for i in 0..<6 {
let pct = Double(i) / 5
var difference = to - from
difference.scale(by: pct)
let currentOpacity = from + difference
print("currentOpacity = \(currentOpacity)")
}
The code will create progressive changes from origin to destination:
该代码将创建从源到目标的渐进式更改:
currentOpacity = 0.3
currentOpacity = 0.4
currentOpacity = 0.5
currentOpacity = 0.6
currentOpacity = 0.7
currentOpacity = 0.8
Why Do I Care About Animatable? 我为什么关心 Animatable?
You may wonder, why do I need to care about all these little details. SwiftUI already animates opacity, without me having to worry about all this. And yes, that is true, but as long as SwiftUI knows how to interpolate values from an origin to a destination. For opacity, it is a straight forward process and SwiftUI knows what to do. However, as we will see next, that is not always the case.
你可能想知道,为什么我需要关心所有这些小细节。SwiftUI 已经为不透明度设置了动画效果,而我不必担心这一切。是的,这是真的,但只要 SwiftUI 知道如何将值从起点插值到终点。对于不透明度,这是一个简单的过程,SwiftUI 知道该怎么做。然而,正如我们接下来将看到的,情况并非总是如此。
There are some big exceptions that come to mind: paths, transform matrices and arbitrary view changes (e.g, the text in a Text view, the gradient colors or stops in a Gradient, etc.). In this cases, the framework does not know what to do. There is no predefined behavior on how to get from A to B. We will discuss transform matrices and view changes, in the upcoming second and third part of this article. For the moment, let’s focus on shapes.
有一些很大的例外:路径、变换矩阵和任意视图更改(例如,文本视图中的文本、渐变中的渐变颜色或渐变停止点等)。在这种情况下,框架不知道该怎么做。关于如何从 A 到 B 没有预定义的行为。我们将在本文的第二部分和第三部分讨论变换矩阵并查看更改。目前,让我们专注于形状。
Animating Shape Paths 对形状路径进行动画处理
Imagine you have a shape that uses a path to draw a regular polygon. Our implementation will of course let you indicate how many sides the polygon will have:
想象一下,您有一个使用路径绘制正多边形的形状。当然,我们的实现将允许您指示多边形将有多少条边:
PolygonShape(sides: 3).stroke(Color.blue, lineWidth: 3)
PolygonShape(sides: 4).stroke(Color.purple, lineWidth: 4)
Here’s our PolygonShape
implementation. Notice that I used a little bit of trigonometry. It is not essential to understand the topic of this post, but if you would like to learn more about it, I wrote another article laying out the basics. You can read more in “Trigonometric Recipes for SwiftUI“.
这是我们的 PolygonShape
实现。请注意,我使用了一点三角函数。了解这篇文章的主题并不重要,但如果你想了解更多关于它的信息,我写了另一篇文章,阐述了基础知识。您可以在“SwiftUI 的三角函数方法”中阅读更多内容。
struct PolygonShape: Shape {
var sides: Int
func path(in rect: CGRect) -> Path {
// hypotenuse (直角三角形的)斜边
let h = Double(min(rect.size.width, rect.size.height)) / 2.0
// center 中心
let c = CGPoint(x: rect.size.width / 2.0, y: rect.size.height / 2.0)
var path = Path()
for i in 0..<sides {
let angle = (Double(i) * (360.0 / Double(sides))) * Double.pi / 180
// Calculate vertex position
let pt = CGPoint(x: c.x + CGFloat(cos(angle) * h), y: c.y + CGFloat(sin(angle) * h))
if i == 0 {
path.move(to: pt) // move to first vertex
} else {
path.addLine(to: pt) // draw line to next vertex
}
}
path.closeSubpath()
return path
}
}
We could take it a little further, and try to animate the side parameter using the same method we used with opacity:
我们可以更进一步,尝试使用与不透明度相同的方法对 side 参数进行动画处理:
PolygonShape(sides: isSquare ? 4 : 3)
.stroke(Color.blue, lineWidth: 3)
.animation(.easeInOut(duration: duration))
How do you think SwiftUI will transform the triangle into a square? You probably guessed it. It won’t. Of course the framework has no idea how to animate it. You may use .animation() all you want, but the shape will jump from a triangle to a square with no animation. The reason is simple: you only taught SwiftUI how to draw a 3-sided polygon, or 4-sided polygon, but you code does not know how to draw a 3.379-sided polygon!
你认为 SwiftUI 会如何将三角形转换为正方形?你可能猜到了。它不会。当然,框架不知道如何为它制作动画。你可以随心所欲地使用 .animation(),但形状会从三角形跳到没有动画的正方形。原因很简单:你只教了 SwiftUI 如何绘制 3 边多边形或 4 边多边形,但你的代码不知道如何绘制 3.379 边多边形!
So, for the animation to happen, we need two things:
因此,要使动画发生,我们需要两件事:
We need to alter the shape code, so it knows how to draw a polygon with a non-integer
sides
number.
我们需要更改形状代码,以便它知道如何绘制具有非整数的多边形 sides
。
Make the framework generate the shape multiple times, with little increments in the animatable parameter. That is, we want the shape be asked to draw multiple times, each time with a different value for the
sides
parameter: 3, 3.1, 3.15, 3.2, 3.25, all the way to 4.
使框架多次生成形状,在可动画参数中增加很少的增量。也就是说,我们希望要求形状多次绘制,每次都使用不同的 sides
参数值:3、3.1、3.15、3.2、3.25,一直到 4。
Once we put that in place, we will be able to animate between any number of sides:
一旦我们把它放到位,我们将能够在任意数量的方面之间制作动画:
Generating Animatable Data 生成可动画数据
To make the shape animatable, we need SwiftUI to render the view multiple times, using all the side values between the origin to the destination number. Fortunately, Shape
already conforms to the Animatable
protocol. This means, there is a computed property (animatableData
), that we can use to handle this task. Its default implementation, however, is set to EmptyAnimatableData
. So it does nothing.
为了使形状可动画化,我们需要 SwiftUI 使用原点到目标编号之间的所有侧值多次渲染视图。幸运的是, Shape
已经符合 Animatable
协议。这意味着,有一个计算属性 ( animatableData
),我们可以用它来处理此任务。但是,其默认实现设置为 EmptyAnimatableData
。所以它什么都不做。
To solve our problem, we will start by changing the type of the sides property, from Int
to Double
. This way we can have decimal numbers. We will discuss later how we can maintain the property as Int
and still perform the animation. But for the moment, to keep things simple, let’s just use Double
.
为了解决我们的问题,我们将首先更改 sides 属性的类型,从 Int
到 Double
。这样我们就可以有十进制数。我们稍后将讨论如何保持属性并 Int
仍然执行动画。但就目前而言,为了简单起见,让我们只使用 Double
.
struct PolygonShape: Shape {
var sides: Double
...
}
Then, we need to create our computed property animatableData. In this case, it’s very simple:
然后,我们需要创建计算属性 animatableData。在这种情况下,非常简单:
struct PolygonShape: Shape {
var sides: Double
var animatableData: Double {
get { return sides }
set { sides = newValue }
}
...
}
Drawing Sides with a Decimal Number
用十进制数绘制边
Finally, we need to teach SwiftUI how to draw a polygon with a non-integer number of sides. We will slightly alter our code. As the decimal part grows, this new side will go from zero to its full length. The other vertices will smoothly reposition accordingly. It sounds complicated, but it is a minimal change:
最后,我们需要教 SwiftUI 如何绘制边数为非整数的多边形。我们将略微更改我们的代码。随着小数部分的增长,这个新边将从零变为全长。其他顶点将相应地平滑地重新定位。这听起来很复杂,但这是一个最小的变化:
func path(in rect: CGRect) -> Path {
// hypotenuse
let h = Double(min(rect.size.width, rect.size.height)) / 2.0
// center
let c = CGPoint(x: rect.size.width / 2.0, y: rect.size.height / 2.0)
var path = Path()
let extra: Int = Double(sides) != Double(Int(sides)) ? 1 : 0
for i in 0..<Int(sides) + extra {
let angle = (Double(i) * (360.0 / Double(sides))) * Double.pi / 180
// Calculate vertex
let pt = CGPoint(x: c.x + CGFloat(cos(angle) * h), y: c.y + CGFloat(sin(angle) * h))
if i == 0 {
path.move(to: pt) // move to first vertex
} else {
path.addLine(to: pt) // draw line to next vertex
}
}
path.closeSubpath()
return path
}
The complete code is available as Example1, in the gist file linked at the top the article.
完整的代码以 Example1 的形式提供,位于文章顶部链接的 gist 文件中。
As mentioned earlier, for a user of our shape, it may seem odd to have the sides
parameter being a Double
. One should expect sides
to be an Int
parameter. Fortunately, we can alter our code yet again, and hide this fact inside our shape’s implementation:
如前所述,对于我们形状的用户来说, sides
将参数设置为 Double
.人们应该期望 sides
是一个 Int
参数。幸运的是,我们可以再次更改我们的代码,并将这一事实隐藏在我们的形状的实现中:
struct PolygonShape: Shape {
var sides: Int
private var sidesAsDouble: Double
var animatableData: Double {
get { return sidesAsDouble }
set { sidesAsDouble = newValue }
}
init(sides: Int) {
self.sides = sides
self.sidesAsDouble = Double(sides)
}
...
}
With these changes, we internally use a Double
, but externally we use an Int
. It looks much more elegant now. And do not forget to modify the drawing code, so it uses sidesAsDouble
instead of sides
. The complete code is available as Example2, in the gist file linked at the top of the article.
通过这些更改,我们在内部使用 Double
,但在外部我们使用 Int
.它现在看起来更优雅了。并且不要忘记修改绘图代码,因此它使用 sidesAsDouble
sides
而不是 .完整的代码以 Example2 的形式提供,位于本文顶部链接的 gist 文件中。
Animating More Than One Parameter 对多个参数进行动画处理
Quite often we will find ourselves needing to animate more than one parameter. A single Double
just won’t cut it. For these moments, we can use AnimatablePair<First, Second>
. Here, First
and Second
are both types that conform to VectorArithmetic
. For example AnimatablePair<CGFloat, Double>
.
很多时候,我们会发现自己需要对多个参数进行动画处理。单打 Double
是不会削减的。对于这些时刻,我们可以使用 AnimatablePair<First, Second>
.这里, First
和 Second
都是符合 VectorArithmetic
的类型。例如 AnimatablePair<CGFloat, Double>
.
To demonstrate the use of AnimatablePair
, we will modify our example. Now our polygon shape will have two parameters: sides
and scale
. Both will be represented with a Double
.
为了演示 AnimatablePair
的用法,我们将修改我们的示例。现在我们的多边形形状将有两个参数: sides
和 scale
.两者都将用 Double
.
struct PolygonShape: Shape {
var sides: Double
var scale: Double
var animatableData: AnimatablePair<Double, Double> {
get { AnimatablePair(sides, scale) }
set {
sides = newValue.first
scale = newValue.second
}
}
...
}
完整的代码以 Example3 的形式提供,位于文章顶部链接的 gist 文件中。Example4 在同一文件中,具有更复杂的路径。它的形状基本相同,但增加了一条线,将每个顶点相互连接起来。
Going Beyond Two Animatable Parameters 超越两个可动画参数
If you browse the SwiftUI declaration file, you will see that the framework uses AnimatablePair
quite extensively. For example: CGSize
, CGPoint
, CGRect
. Although these types do not conform to VectorArithmetic
, they can be animated, because they do conform to Animatable
.
如果您浏览 SwiftUI 声明文件,您会看到该框架的使用 AnimatablePair
非常广泛。例如: CGSize
、、 CGPoint
CGRect
。尽管这些类型不符合 ,但它们可以进行动画处理 VectorArithmetic
,因为它们确实符合 Animatable
。
They all use AnimatablePair
in one way or another:
它们都以一种或另一种方式使用 AnimatablePair
:
extension CGPoint : Animatable {
public typealias AnimatableData = AnimatablePair<CGFloat, CGFloat>
public var animatableData: CGPoint.AnimatableData
}
extension CGSize : Animatable {
public typealias AnimatableData = AnimatablePair<CGFloat, CGFloat>
public var animatableData: CGSize.AnimatableData
}
extension CGRect : Animatable {
public typealias AnimatableData = AnimatablePair<CGPoint.AnimatableData, CGSize.AnimatableData>
public var animatableData: CGRect.AnimatableData
}
If you pay closer attention to CGRect, you will see that it is actually using:
如果你仔细观察CGRect,你会发现它实际上是在使用:
AnimatablePair<AnimatablePair<CGFloat, CGFloat>, AnimatablePair<CGFloat, CGFloat>>
This means the rectangle x, y, width and height values are accessible through first.first
, first.second
, second.first
and second.second
.
这意味着矩形 x、y、width 和 height 值可通过 first.first
、 first.second
和 second.first
second.second
访问。
Making Your Own Type Animatable (with VectorArithmetic) 使自己的类型可动画化(使用 VectorArithmetic)
The following types conform to Animatable: Angle
, CGPoint
, CGRect
, CGSize
, EdgeInsets
, StrokeStyle
and UnitPoint
. And the following types conform to VectorArithmetic: AnimatablePair
, CGFloat
, Double
, EmptyAnimatableData
and Float
. You can use any of them to animate your shape.
以下类型默认实现了 Animatable
: Angle
, CGPoint
, CGRect
, CGSize
, EdgeInsets
, StrokeStyle
和 UnitPoint
。以下类型符合VectorArithmetic
: AnimatablePair
, CGFloat
, Double
, EmptyAnimatableData
和 Float
。你可以使用它们中的任何一种来为你的形状制作动画。
The existing types offer enough flexibility to animate anything. However, if you find yourself with a complex type you want to animate, nothing prevents you from adding your own implementation of the VectorArithmetic conformance. In fact, we will do it in the next example.
现有的类型提供了足够的灵活性来实现任何东西的动画。然而,如果你发现自己有一个想做动画的复杂类型,没有什么能阻止你添加自己的VectorArithmetic
协议的实现。事实上,我们将在下一个例子中这样做。
To illustrate, we are going to create an analog Clock shape. It will move its needles according to a custom animatable parameter of type: ClockTime.
为了说明这一点,我们将创建一个模拟时钟形状。它将根据以下类型的自定义可动画参数移动其指针: ClockTime.
We are going to be using it like this:
我们将像这样使用它:
ClockShape(clockTime: show ? ClockTime(9, 51, 15) : ClockTime(9, 55, 00))
.stroke(Color.blue, lineWidth: 3)
.animation(.easeInOut(duration: duration))
First, we begin by creating our custom type ClockTime. It contains three properties (hours, minutes and seconds), a couple of useful initializers, and some helper computed properties and methods:
首先,我们首先创建自定义类型 ClockTime。它包含三个属性(小时、分钟和秒)、几个有用的初始值设定项以及一些帮助程序计算的属性和方法:
struct ClockTime {
var hours: Int // Hour needle should jump by integer numbers
var minutes: Int // Minute needle should jump by integer numbers
var seconds: Double // Second needle should move smoothly
// Initializer with hour, minute and seconds
init(_ h: Int, _ m: Int, _ s: Double) {
self.hours = h
self.minutes = m
self.seconds = s
}
// Initializer with total of seconds
init(_ seconds: Double) {
let h = Int(seconds) / 3600
let m = (Int(seconds) - (h * 3600)) / 60
let s = seconds - Double((h * 3600) + (m * 60))
self.hours = h
self.minutes = m
self.seconds = s
}
// compute number of seconds
var asSeconds: Double {
return Double(self.hours * 3600 + self.minutes * 60) + self.seconds
}
// show as string
func asString() -> String {
return String(format: "%2i", self.hours) + ":" + String(format: "%02i", self.minutes) + ":" + String(format: "%02f", self.seconds)
}
}
Now, to conform to VectorArithmetic, we need to write the following methods and computed properties:
现在,为了符合 VectorArithmetic,我们需要编写以下方法和计算属性:
extension ClockTime: VectorArithmetic {
static var zero: ClockTime {
return ClockTime(0, 0, 0)
}
var magnitudeSquared: Double { return asSeconds * asSeconds }
static func -= (lhs: inout ClockTime, rhs: ClockTime) {
lhs = lhs - rhs
}
static func - (lhs: ClockTime, rhs: ClockTime) -> ClockTime {
return ClockTime(lhs.asSeconds - rhs.asSeconds)
}
static func += (lhs: inout ClockTime, rhs: ClockTime) {
lhs = lhs + rhs
}
static func + (lhs: ClockTime, rhs: ClockTime) -> ClockTime {
return ClockTime(lhs.asSeconds + rhs.asSeconds)
}
mutating func scale(by rhs: Double) {
var s = Double(self.asSeconds)
s.scale(by: rhs)
let ct = ClockTime(s)
self.hours = ct.hours
self.minutes = ct.minutes
self.seconds = ct.seconds
}
}
The only thing left to do, is writing the shape to position the needles appropriately. The full code of the Clock shape, is available as Example5 in the gist file linked at the top of this article.
剩下唯一要做的就是写出形状以适当地放置针头。时钟形状的完整代码在本文顶部链接的 gist 文件中以 Example5 的形式提供。
SwiftUI + Metal SwiftUI + 金属
If you find yourself coding complex animations, you may start to see your device suffering, while trying to keep up with all the drawing. If so, you could definitely benefit from enabling the use of Metal. Here’s an example of how having Metal enabled, makes all the difference:
When running on the simulator, you may not perceive much difference. However, on the real device, you will. The video demonstration is from an iPad 6th generation (2016). Full code is in the gist file, under name Example6.
在模拟器上运行时,您可能不会感觉到太大的差异。但是,在真实设备上,您会。视频演示来自第 6 代 iPad(2016 年)。完整代码位于 gist 文件中,名称为 Example6。
Fortunately, enabling Metal, is extremely easy. You just need to add the .drawingGroup()
modifier:
幸运的是,启用 Metal 非常容易。您只需要添加 .drawingGroup()
修饰符:
FlowerView().drawingGroup()
According to WWDC 2019, Session 237 (Building Custom Views with SwiftUI): A drawing group is a special way of rendering but only for things like graphics. It will basically flatten the SwiftUI view into a single NSView/UIView and render it with metal. Jump the WWDC video to 37:27 for a little more detail.
根据 WWDC 2019 第 237 节(使用 SwiftUI 构建自定义视图):绘图组是一种特殊的渲染方式,但仅适用于图形等内容。它基本上会将 SwiftUI 视图展平为单个 NSView/UIView,并使用 Metal 渲染它。将 WWDC 视频跳转到 37:27 以获取更多详细信息。
If you want to try it out, but your shapes are not complex enough to make the device struggle, add some gradients and shadows and you will immediately see the difference.
如果你想尝试一下,但你的形状不够复杂,不会让设备挣扎,添加一些渐变和阴影,你会立即看到差异。
What’s Next 下一步是什么
In the second part of this article, we will learn how to use the GeometryEffect protocol. It will open the door to new ways of altering our views and animating them. As with Paths, SwiftUI has no built-in knowledge on how it can transition between two different transform matrices. GeometryEffect will be helpful in doing so.
在本文的第二部分,我们将学习如何使用 GeometryEffect 协议。它将为改变我们的观点和动画化的新方法打开大门。与 Path 一样,SwiftUI 没有关于如何在两个不同的转换矩阵之间转换的内置知识。GeometryEffect 对此很有帮助。
At this moment, SwiftUI has no keyframe functionality. We will see how we can emulate one with a basic animation.
目前,SwiftUI 没有关键帧功能。我们将看到如何用基本动画来模拟一个。
In the third part of the article we will introduce AnimatableModifier, a very powerful tool that will let us animate anything that can change in a View, even Text! For some examples of the animations included in this 3 part series, check the video below:
在本文的第三部分,我们将介绍 AnimatableModifier,这是一个非常强大的工具,可以让我们对视图中任何可以更改的内容进行动画处理,甚至是文本!有关此 3 部分系列中包含的动画的一些示例,请查看下面的视频:
Advanced SwiftUI Animations
高级 SwiftUI 动画
Make sure you don’t miss the next part of the article when it becomes available. And please feel free to comment. You can also follow me on twitter, if you want to be notified when new articles come out. The link’s below. Until then…
确保你不会错过文章的下一部分。请随时发表评论。您也可以在Twitter上关注我,如果您想在新文章发布时收到通知。链接如下。在那之前...