2025-02-05 10:52:57
,某些文章具有时效性,若有错误或已失效,请在下方留言。一个容器,通过自动循环遍历你提供的一系列阶段来对其内容进行动画处理,每个阶段定义了动画中的一个离散步骤。
具有阶段动画的弹跳
例如,前面展示的表情符号弹跳动画具有两个阶段:向上移动和返回。你可以使用布尔值true
和false
来表示这些阶段。当阶段为true
时,表情符号向上移动至-40.0。当阶段为false
时,通过将偏移量设置为0.0,表情符号返回到原始位置。
struct TwoPhaseAnimationView: View {
var emoji: String
var body: some View {
EmojiView(emoji: emoji)
.phaseAnimator([false, true]) { content, phase in
content.offset(y: phase ? -40.0 : 0.0)
}
}
}
例如,以下代码每次有人点击表情符号时都会递增状态变量likeCount。代码使用likeCount作为阶段动画器观察变化的值。现在,每当有人点击表情符号时,它都会向上移动并返回到其原始位置。
struct TwoPhaseAnimationView: View {
var emoji: String
@State private var likeCount = 1
var body: some View {
EmojiView(emoji: emoji)
.phaseAnimator([false, true], trigger: likeCount) { content, phase in
content.offset(y: phase ? -40.0 : 0.0)
}
.onTapGesture {
likeCount += 1
}
}
}
例如,当阶段为 true
时,以下代码应用弹性动画;否则,应用默认动画:
struct TwoPhaseAnimationView: View {
var emoji: String
@State private var likeCount = 1
var body: some View {
EmojiView(emoji: emoji)
.phaseAnimator([false, true], trigger: likeCount) { content, phase in
content.offset(y: phase ? -40.0 : 0.0)
} animation: { phase in
phase ? .bouncy : .default
}
.onTapGesture {
likeCount += 1
}
}
}
为动画添加更多阶段
例如,您可以使表情符号在向上移动时增大,然后再缩小到正常大小。为此,您将向动画添加第三个阶段:缩放。
要定义这些阶段,请创建一个列出可能阶段的自定义类型;例如:
private enum AnimationPhase: CaseIterable {
case initial
case move
case scale
}
接下来,为了简化逻辑并降低复杂性,定义计算属性来返回要动画的值。例如,要设置垂直偏移量以移动表情符号,请创建一个计算属性,根据当前阶段返回偏移量:
private enum AnimationPhase: CaseIterable {
case initial
case move
case scale
var verticalOffset: Double {
switch self {
case .initial: return 0
case .move, .scale: return -64
}
}
}
当处于初始阶段时,偏移量为 O,这是表情符号在屏幕上的原始位置。但是当阶段是 move 或 scale 时,偏移量为-64。
您可以使用相同的方法(创建计算属性)来实现缩放效果,以改变表情符号的大小。最初,表情符号以其原始大小出现,但在移动和缩放阶段增大,如下所示:
private enum AnimationPhase: CaseIterable {
case initial
case move
case scale
var verticalOffset: Double {
switch self {
case .initial: 0
case .move, .scale: -64
}
}
var scaleEffect: Double {
switch self {
case .initial: 1
case .move, .scale: 1.5
}
}
}
要对表情符号进行动画处理,请将 phaseAnimator(_:trigger:content:animation:)
修饰符应用于EmojiView。向动画器提供来自自定义AnimationPhase类型的所有情况。然后根据阶段改变内容,通过应用 scaleEffect(_:anchor:)
和 offset(x:y:)
修饰符。这些修饰符传入的值来自计算属性,有助于保持视图代码更易读。
struct ThreePhaseAnimationView: View {
var emoji: String
@State private var likeCount = 1
var body: some View {
EmojiView(emoji: emoji)
.phaseAnimator(AnimationPhase.allCases, trigger: likeCount) { content, phase in
content
.scaleEffect(phase.scaleEffect)
.offset(y: phase.verticalOffset)
} animation: { phase in
switch phase {
case .initial: .smooth
case .move: .easeInOut(duration: 0.3)
case .scale: .spring(duration: 0.3, bounce: 0.7)
}
}
.onTapGesture {
likeCount += 1
}
}
}
代码还根据动画闭包中的阶段应用不同的动画类型,以赋予动画以更多的生气。
使用关键帧动画器获得更多控制
您可以使用 KeyframeAnimator 定义复杂的、协调的动画,并完全控制时间和运动。这个动画器允许您创建关键帧,这些关键帧在动画过程中的特定时间定义值。动画器使用这些值来在动画的每一帧之间生成插值值。
与阶段动画器不同,在阶段动画器中,您建模单独的、离散的状态,而关键帧动画器生成您指定类型的插值值。在动画进行中,动画器为您提供此类型的值,因此您可以通过对其应用修饰符来更新动画视图。
您可以将该类型定义为包含您想要独立动画的属性的结构。例如,以下代码定义了四个属性,确定表情符号的缩放、拉伸、位置和角度:
private struct AnimationValues {
var scale = 1.0
var verticalStretch = 1.0
var verticalOffset = 0.0
var angle = Angle.zero
}
KeyframeAnimator
可以对符合 Animatable
协议的任何值进行动画处理。
要使用关键帧动画器创建动画,请将 keyframeAnimator(initialValue:repeating:content:keyframes:)
或keyframeAnimator(initialValue:trigger:content:keyframes:)
修饰符应用于要动画处理的视图。例如,以下代码将第二个修饰符应用于 EmojiView
。动画的初始值是一个 Animationvalues
的新实例,状态变量 likeCount 是动画器观察变化的值,就像在之前的阶段动画示例中一样。
struct KeyframeAnimationView: View {
var emoji: String
@State private var likeCount = 1
var body: some View {
EmojiView(emoji: emoji)
.keyframeAnimator(
initialValue: AnimationValues(),
trigger: likeCount
) { content, value in
// ...
} keyframes: { _ in
// ...
}
.onTapGesture {
likeCount += 1
}
}
}
在动画期间对视图应用修饰符,请为关键帧动画器提供一个 content
闭包。此闭包包括两个参数:
• content
正在进行动画的视图。
• value
当前的插值值。
使用这些参数来对 SwiftUI 正在动画处理的视图应用修饰符。例如,以下代码使用这些参数来旋转、缩放、拉伸和移动一个表情符号:
struct KeyframeAnimationView: View {
var emoji: String
@State private var likeCount = 1
var body: some View {
EmojiView(emoji: emoji)
.keyframeAnimator(
initialValue: AnimationValues(),
trigger: likeCount
) { content, value in
content
.rotationEffect(value.angle)
.scaleEffect(value.scale)
.scaleEffect(y: value.verticalStretch)
.offset(y: value.verticalOffset)
} keyframes: { _ in
// ...
}
.onTapGesture {
likeCount += 1
}
}
}
SwiftUI 在动画的每一帧上调用关键帧动画器的 content
闭包,因此避免直接在其中执行任何昂贵的操作。
接下来,定义关键帧。关键帧让您可以使用不同的关键帧为不同的属性构建复杂的动画。为了实现这一点,您将关键帧组织成轨道。每个轨道控制您正在动画处理的类型的不同属性。通过在创建轨道时提供属性的关键路径,您将属性与轨道关联起来。例如,以下代码为 scale
属性添加了一个 KeyframeTrack
:
struct KeyframeAnimationView: View {
var emoji: String
@State private var likeCount = 1
var body: some View {
EmojiView(emoji: emoji)
.keyframeAnimator(
initialValue: AnimationValues(),
trigger: likeCount
) { content, value in
content
.rotationEffect(value.angle)
.scaleEffect(value.scale)
.scaleEffect(y: value.verticalStretch)
.offset(y: value.verticalOffset)
} keyframes: { _ in
KeyframeTrack(\.scale) {
// ...
}
}
.onTapGesture {
likeCount += 1
}
}
}
在创建轨道时,您可以在SwiftUl中使用声明性语法向轨道添加关键帧。有不同类型的关键帧,如 Cubickeyframe
、Linearkeyframe
和 springKeyframe
。您可以在轨道内混合和匹配不同类型的关键帧。例如,以下代码为 scale
属性添加了一个轨道,执行线性和弹簧动画的组合:
struct KeyframeAnimationView: View {
var emoji: String
@State private var likeCount = 1
var body: some View {
EmojiView(emoji: emoji)
.keyframeAnimator(
initialValue: AnimationValues(),
trigger: likeCount
) { content, value in
content
.rotationEffect(value.angle)
.scaleEffect(value.scale)
.scaleEffect(y: value.verticalStretch)
.offset(y: value.verticalOffset)
} keyframes: { _ in
KeyframeTrack(\.scale) {
LinearKeyframe(1.0, duration: 0.36)
SpringKeyframe(1.5, duration: 0.8,
spring: .bouncy)
SpringKeyframe(1.0, spring: .bouncy)
}
}
.onTapGesture {
likeCount += 1
}
}
}
每种关键帧类型都接收一个值。动画器使用此值在帧之间生成插值值,并在调用动画器的内容闭包之前设置轨道的关键路径中指定的属性。例如,在上面的代码清单中,线性关键帧期间的缩放值为 1.0,这会保持表情符号的原始大小。然后,在第一个弹簧关键帧期间,缩放变为 1.5。这导致表情符号变大。最后一个弹簧关键帧将缩放设置为 1.0,将表情符号返回到其原始大小。
SwiftUl 在轨道内的连续运动中跨多个关键帧保留速度(即动画的速度)。
在实现关键帧动画时,为每个要动画处理的属性包括一个轨道。例如,
scale
verticalStretch
verticalOffset
angle
要对所有四个属性进行动画处理,动画器需要四个关键帧轨道,如下面的代码所示: AnimationValues
有四个属性:
struct KeyframeAnimationView: View {
var emoji: String
@State private var likeCount = 1
var body: some View {
EmojiView(emoji: emoji)
.keyframeAnimator(
initialValue: AnimationValues(),
trigger: likeCount
) { content, value in
content
.rotationEffect(value.angle)
.scaleEffect(value.scale)
.scaleEffect(y: value.verticalStretch)
.offset(y: value.verticalOffset)
} keyframes: { _ in
KeyframeTrack(\.scale) {
LinearKeyframe(1.0, duration: 0.36)
SpringKeyframe(1.5, duration: 0.8, spring: .bouncy)
SpringKeyframe(1.0, spring: .bouncy)
}
KeyframeTrack(\.verticalOffset) {
LinearKeyframe(0.0, duration: 0.1)
SpringKeyframe(20.0, duration: 0.15, spring: .bouncy)
SpringKeyframe(-60.0, duration: 1.0, spring: .bouncy)
SpringKeyframe(0.0, spring: .bouncy)
}
KeyframeTrack(\.verticalStretch) {
CubicKeyframe(1.0, duration: 0.1)
CubicKeyframe(0.6, duration: 0.15)
CubicKeyframe(1.5, duration: 0.1)
CubicKeyframe(1.05, duration: 0.15)
CubicKeyframe(1.0, duration: 0.88)
CubicKeyframe(0.8, duration: 0.1)
CubicKeyframe(1.04, duration: 0.4)
CubicKeyframe(1.0, duration: 0.22)
}
KeyframeTrack(\.angle) {
CubicKeyframe(.zero, duration: 0.58)
CubicKeyframe(.degrees(16), duration: 0.125)
CubicKeyframe(.degrees(-16), duration: 0.125)
CubicKeyframe(.degrees(16), duration: 0.125)
CubicKeyframe(.zero, duration: 0.125)
}
}
.onTapGesture {
likeCount += 1
}
}
}
这些关键帧轨道的组合创建了一个动画,将表情符号压缩和拉伸,然后将其向上弹起。当表情符号向其最高点移动时,它变得更大。当表情符号达到最高点时,它会稍微摆动。然后,当表情符号回到原始位置时,它会稍微弹跳,以回到原来的位置。
与阶段动画一样,使用Xcode中的画布预览来帮助确定要应用于关键帧动画的动画类型和值。对代码进行更改,并在画布预览中看到这些更改的反映。
暂无评论内容