动画(四) – 过渡

动画(四) – 过渡

温馨提示:本文最后更新于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 类型所取代,该类型区分了 willAppearidentitydidDisappear 阶段。

在⾃定义过渡⾥,除了创建不对称的过渡,我们还可以使⽤ 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)
        }
    }
}
动画效果

动画效果

动画的执行过程:

  1. 初始阶段是数组中的第⼀个元素 (0),按钮按照 offset(x: 0) 进⾏渲染。
  2. shakes 属性发⽣更改时,阶段动画器将其内部状态更改为下⼀个阶段 (-20)。内部状态变化会被包装在⼀个显式动画中。
  3. 当该动画完成时,动画器再次使⽤动画将其状态更改到第三个阶段 (20)。
  4. 最后,当该动画完成时,动画器将其状态再次更改为初始状态。

基于关键帧的动画

关键帧动画是⼀种描述动画的⽅法,动画在关键帧所指定的离散值之间进⾏过渡。

关键帧动画会随时间对 单⼀值 进⾏动画。这个值可以是⼀个双精度浮点数,也可以是⼀个包含多个属性的结构体 —— 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》

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容