2024-12-17 21:19:52
,某些文章具有时效性,若有错误或已失效,请在下方留言。以视图的插⼊和移除来设定动画,SwiftUI
将这些动画称为 过渡 (transition) 。
当我们使⽤动画来改变那些会导致视图插⼊或者移除的状态时,SwiftUI 会添加⼀个默认的 .opacity
过渡效果。
struct ContentView: View {
@State private var flag = false
var body: some View {
VStack {
Button("Toogle") {
withAnimation {
flag.toggle()
}
}
if flag {
Rectangle()
.frame(width: 100, height: 100)
}
}
}
}

点击时矩形的显示和消失会进⾏淡⼊淡出动画,这正是默认的 .opacity
过渡的表现。
过渡有两种状态,分别是 活动 (active)
状态和 常时 (identity)
状态。当⼀个视图被插⼊时,过渡效果会从活动状态变为常时状态。当移除时,它从常时状态变为活动状态。

动画过程
可以在 SwiftUI 的 AnyTransition.modifier(active:identity:) API
⾥⽤过渡的这两个状态来构建⾃定义的过渡效果。该⽅法的两个参数都接受定义对应状态的视图修饰符。
struct Blur: ViewModifier {
var radius: CGFloat
func body(content: Content) -> some View {
content
.blur(radius: radius)
}
}
extension AnyTransition {
static func blur(radius: CGFloat) -> Self {
.modifier(active: Blur(radius: 5), identity: Blur(radius: 0))
}
}
struct ContentView: View {
@State private var flag = false
var body: some View {
VStack {
Button("Toogle") {
withAnimation {
flag.toggle()
}
}
if flag {
Rectangle()
.frame(width: 100, height: 100)
.transition(.blur(radius: 5))
}
}
.animation(.default, value: flag)
}
}
过渡
始终是和动画
⼀起使⽤的。仅仅将⼀个 .transition
修饰符放到视图上,并不会导致视图⽤动画的⽅式进⾏改变。为了让过渡发⽣,我们总是需要在 .transition
修饰符旁边使⽤⼀个显式动画或者隐式动画。

动画效果
SwiftUI 提供了⼀系列内置的过渡效果,⽐如 .slide
,.move(edge:)
和 .scale(scale:anchor:)
以及其他更多效果,我们还可以使⽤ .combined(with:)
过渡把它们组合起来同时运⾏。想为插⼊和移除指定不同的过渡效果,也可以使⽤ .asymmetric(insertion:removal:)
。
现在可以通过创建⼀个遵守 Transition
协议的⾃定义类型,来创建⾃定义过渡了。活动状态和常时状态的概念已被明确的 TransitionPhase
类型所取代,该类型区分了 willAppear
、identity
和 didDisappear
阶段。
在⾃定义过渡⾥,除了创建不对称的过渡,我们还可以使⽤ TransitionPhase
来明确区分插⼊、常时和移除。
基于阶段的动画
阶段动画器 (phase animator) 是实现“回到原点”动画最简单的⽅式。这些阶段是离散的数值,阶段动画器会依次遍历这些数值:⼀旦⼀个阶段完成,系统就会继续执⾏下⼀个阶段。阶段动画器的持续时间可以是⽆限的 (当完成最后⼀个阶段时,它会回到初始阶段并继续),或者也可以在每次触发值的变化时才循环遍历⼀次所有阶段。
struct ContentView: View {
@State private var shakes = 0
var body: some View {
Button("Shake!") {
shakes += 1
}
.phaseAnimator([0, -20, 20], trigger: shakes) { content, offset in
content.offset(x: offset)
}
}
}

动画效果
动画的执行过程:
- 初始阶段是数组中的第⼀个元素 (0),按钮按照 offset(x: 0) 进⾏渲染。
- 当
shakes
属性发⽣更改时,阶段动画器将其内部状态更改为下⼀个阶段 (-20)。内部状态变化会被包装在⼀个显式动画中。 - 当该动画完成时,动画器再次使⽤动画将其状态更改到第三个阶段 (20)。
- 最后,当该动画完成时,动画器将其状态再次更改为初始状态。
基于关键帧的动画
关键帧动画是⼀种描述动画的⽅法,动画在关键帧所指定的离散值之间进⾏过渡。
关键帧动画会随时间对 单⼀值 进⾏动画。这个值可以是⼀个双精度浮点数,也可以是⼀个包含多个属性的结构体 —— SwiftUI 对值的类型没有进⾏任何限制。
关键帧动画由⼀个或多个轨道 (track) 构成,每个属性有⼀个轨道。
单轨道
struct ContentView: View {
@State private var trigger = 0
var body: some View {
Button("Shake!") {
trigger += 1
}
.keyframeAnimator(initialValue: 0, trigger: trigger) { content, offset in
content
.offset(x: offset)
} keyframes: { value in
CubicKeyframe(-30, duration: 0.25)
CubicKeyframe(30, duration: 0.5)
CubicKeyframe(0, duration: 0.25)
}
}
}

动画效果
每当触发值改变时,动画就会运⾏。偏移量将从 0 开始,第⼀个关键帧在开始的 0.25 秒内向 -30 进⾏动画。第⼆个关键帧在接下来的半秒内向30动画,最后⼀个关键帧向 0 动画。由于我们选择了三次插值 (cubic interpolation) 的⽅式,动画将 (从初始速度0开始) 平稳向上,并在关键帧之间平滑过渡。下⾯是显示随时间绘制的值的图表 (点表示关键帧):

动画过程
多个轨道
⾸先,我们定义⼀个结构体,该结构体具有两个属性,来持有我们想要进⾏动画的值:
struct ShakeData {
var offset: CGFloat = 0
var rotation: Angle = .zero
}
struct ContentView: View {
@State private var trigger = 0
var body: some View {
Button("Shake!") {
trigger += 1
}
.keyframeAnimator(initialValue: ShakeData(), trigger: trigger) { content, data in
content
.offset(x: data.offset)
.rotationEffect(data.rotation)
} keyframes: { value in
KeyframeTrack(\.offset) {
CubicKeyframe(-30, duration: 0.25)
CubicKeyframe(30, duration: 0.5)
CubicKeyframe(0, duration: 0.25)
}
KeyframeTrack(\.rotation) {
LinearKeyframe(.degrees(20), duration: 0.1)
LinearKeyframe(.degrees(-20), duration: 0.2)
LinearKeyframe(.degrees(20), duration: 0.2)
LinearKeyframe(.degrees(-20), duration: 0.2)
LinearKeyframe(.degrees(20), duration: 0.2)
LinearKeyframe(.degrees(0), duration: 0.1)
}
}
}
}

动画效果
⽤第⼀个轨道来设定偏移量动画,⽤第⼆个轨道来设定旋转值动画。虽然单个轨道内的关键帧是按顺序运⾏的,但轨道本身是并⾏的。

动画过程
⼀般来说,不同轨道的持续时间不必相同。如果其他轨道仍在运⾏时,某个轨道就已经结束了,那么这个结束的轨道将把最终值报告给动画的其余部分。
参考 《Thinking In SwiftUI》
暂无评论内容