IOS14 SwiftUI - Animation

2,236 阅读4分钟

SwiftUI提供两种类型的动画:隐式和显式。两种方法都可以为视图设置动画和视图过渡。为了实现隐式动画,框架提供了一个称为的修饰符animation。可以将此修改器附加到要设置动画的视图,并指定首选的动画类型。(可选)可以定义动画的持续时间和延迟。然后,SwiftUI将根据视图的状态变化自动渲染动画。显式动画对您要呈现的动画提供了更有限的控制。无需向视图附加​​修饰符,而是告诉SwiftUI您要在**withAnimation()**块内设置动画的状态变化。

隐式动画

看一下上图。这是一个简单的可点击视图,由红色圆圈和心脏组成。当用户点击心脏或圆圈时,圆圈的颜色将更改为浅灰色,而心脏的颜色将更改为红色。同时,心脏图标的大小会变大。因此,我们在这里进行各种状态更改:

  1. 圆圈的颜色从红色变为浅灰色。
  2. 心脏图标的颜色从白色变为红色。
  3. 心形图标将其原始大小加倍。

如果使用SwiftUI实现可轻按的圆圈,则代码如下所示:

//
//  ContentView.swift
//  ContentView
//
//  Created by Apple on 2021/2/5.
//

import SwiftUI

struct ContentView: View {
    @State private var circleColorChanged = false
    @State private var heartColorChanged  = false
    @State private var heartSizeChanged   = false
    
    
    var body: some View {
        ZStack {
                   Circle()
                       .frame(width: 200, height: 200)
                       .foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
                      

                   Image(systemName: "heart.fill")
                       .foregroundColor(heartColorChanged ? .red : .white)
                       .font(.system(size: 100))
                       .scaleEffect(heartSizeChanged ? 1.0 : 0.5)
                       
               }
//               .animation(.default)
                .animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3))
                .onTapGesture {
                   self.circleColorChanged.toggle()
                   self.heartColorChanged.toggle()
                   self.heartSizeChanged.toggle()
               }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

我们定义了三个状态变量,以将初始值设置为false来对状态进行建模。要创建圆圈和心脏,我们使用ZStack将心脏图像叠加在圆圈顶部。SwiftUI带有onTapGesture用于检测轻击手势的修饰符。可以将其附加到任何视图以使其可点击。在onTapGesture闭包中,我们切换状态以更改视图的外观。

.animation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3))

这将渲染基于弹簧的动画,使心脏产生颠簸的效果。您可以调整阻尼和混合值以获得不同的效果。

显式动画

让我们看看如何使用显式动画获得相同的结果。如前所述,您需要将状态更改包装在withAnimation块中。要创建相同的动画效果,可以编写如下代码:

  ZStack {
            Circle()
                .frame(width: 200, height: 200)
                .foregroundColor(circleColorChanged ? Color(.systemGray5) : .red)
         
            Image(systemName: "heart.fill")
                .foregroundColor(heartColorChanged ? .red : .white)
                .font(.system(size: 100))
                .scaleEffect(heartSizeChanged ? 1.0 : 0.5)
        }
        .onTapGesture {
            withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
                self.circleColorChanged.toggle()
                self.heartColorChanged.toggle()
                self.heartSizeChanged.toggle()
            }
        }

我们不再使用的animation而不是我们总结的代码中onTapGesture有withAnimationwithAnimation调用采用动画参数。

使用显式动画,可以轻松控制要动画的状态。例如,如果您不希望对心脏图标的大小进行动画处理,则可以从以下代码中排除该行代码withAnimation

.onTapGesture {
    withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {
        self.circleColorChanged.toggle()
        self.heartColorChanged.toggle()
    }
 
    self.heartSizeChanged.toggle()
}

在这种情况下,SwiftUI将仅对圆圈和心脏的颜色变化进行动画处理。您将不再看到心脏图标的动画效果。

使用RotationEffect创建加载指示器

SwiftUI动画的强大功能在于您无需担心如何对视图进行动画处理。您需要提供的是开始和结束状态。然后,SwiftUI会找出其余的内容。如果您了解此概念,则可以创建各种类型的动画。

例如,让我们创建一个简单的加载指示器,创建如上图所示的加载指示器,我们可以从如下所示的开放式圆圈开始:

import SwiftUI

struct ContentView: View {
    @State private var isLoading = false
    
    
    var body: some View {
        
    
        ZStack {
           
            Circle()
                       .stroke(Color(.systemGray5), lineWidth: 14)
                       .frame(width: 100, height: 100)
        
                   Circle()
                       .trim(from: 0, to: 0.2)
                       .stroke(Color.green, lineWidth: 7)
                       .frame(width: 100, height: 100)
                       .rotationEffect(Angle(degrees: isLoading ? 360 : 0))
                       .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
                       .onAppear() {
                           self.isLoading = true
                   }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

加载指示器不必是圆形的。您也可以使用RectangleRoundedRectangle创建指标。但是您可以更改偏移量的值来创建动画,而无需更改旋转角度。

为了创建动画,我们将两个圆角矩形叠加在一起。上方的矩形比下方的矩形短得多。加载开始时,我们将其偏移值从-110更新为110。

import SwiftUI

struct ContentView: View {
 
    @State private var isLoading = false
 
    var body: some View {
        ZStack {
 
            Text("Loading")
                .font(.system(.body, design: .rounded))
                .bold()
                .offset(x: 0, y: -25)
 
            RoundedRectangle(cornerRadius: 3)
                .stroke(Color(.systemGray5), lineWidth: 3)
                .frame(width: 250, height: 3)
 
            RoundedRectangle(cornerRadius: 3)
                .stroke(Color.green, lineWidth: 3)
                .frame(width: 30, height: 3)
                .offset(x: isLoading ? 110 : -110, y: 0)
                .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))
        }
        .onAppear() {
            self.isLoading = true
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

这将使绿色矩形沿线移动。并且,当您一遍又一遍地重复相同的动画时,它将变成加载动画。下图说明了偏移值。