[SwiftUI] 显式动画和隐式动画

2,189 阅读2分钟

更多内容,欢迎关注公众号 「Swift花园」

喜欢文章?不如来个 🔺💛➕三连?关注专栏,关注我 🚀🚀🚀

显式动画和隐式动画

SwiftUI 里有两种动画,显式的和隐式的。隐式动画通过 .animation() modifier 指定。每当视图的某个 animatable 参数改变时,SwiftUI 会动画化呈现旧值到新值的变化。这些 animatable 参数包括尺寸,偏移量,颜色,缩放值,等等。

显式动画通过 withAnimation { ... } 闭包指定。只有那些依赖 withAnimation 闭包中的值变化的参数才会被动画化。让我们举例说明:

下面的例子显示了采用隐式动画来呈现图片缩放和改变透明度的过程:

tower1
隐式动画

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()
            }
    }
}

下面的例子则使用了显式动画。这里的缩放值和透明度都发生变化,但只有透明度变化动画化,因为它是唯一放进 withAnimation 闭包中的参数:

tower2
显式动画

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()
                }
        }
    }
}

注意,你可以保持使用隐式动画,但通过改变 modifier 放置的顺序,达到和显式动画一样的效果:

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()
        }
    }
}

假如你需要禁用动画,可以使用 .animation(nil)

动画的工作机制

在所有的 SwiftUI 动画幕后,有一个叫 Animatable 的协议。关于它的具体细节我们之后还会介绍,总的来说,它要求包含一个返回 VectorArithmetic 类型的计算属性,以便框架能够对值进行插值操作。

在动画化一个视图时,SwiftUI 实际上会多次重新生成视图,同时修改动画参数。借助这种方式,我们能逐渐地从原始值过渡到最终值。

假设我们为某个视图的不透明度变化创建线性动画,从 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)")
}

上面的代码能够实现从原始点到目标点之间的渐进值:

currentOpacity = 0.3
currentOpacity = 0.4
currentOpacity = 0.5
currentOpacity = 0.6
currentOpacity = 0.7
currentOpacity = 0.8

我的公众号 这里有Swift及计算机编程的相关文章,以及优秀国外文章翻译,欢迎关注~

Swift花园微信公众号