百日学 Swift(Day 32) – 项目 6 :第 1 部分

百日学 Swift(Day 32) – Project 6, part one(项目 6 :第 1 部分)

1. Animation: Introduction(项目介绍)

本项目研究的是动画和过渡。先创建项目吧,项目名称 Animations。本节内容因为是介绍和动画相关的语法,所以内容叙述上较为分散。

2. Creating implicit animations(创建隐式动画)

(1)先随便弄个按钮出来。

Button("点我") {
    // 先空着
}
.padding(50)				// 数字会决定按钮的大小
.background(Color.red)		// 前景色,
.foregroundColor(.white)	// 背景色,
.clipShape(Circle())		// 圆形,为了后面的演示
// 这些修饰的具体内容可以随便你高兴去具体设定

(2)增加动画状态属性

@State private var animationAmount: CGFloat = 1

这里应该注意的是,因为涉及与旧的 API 交互,所以要使用一种称为 CGFloat 的数据类型,它和 Double 很像,但是比 Double 存储的数值范围要小一些。这是考虑到较老的硬件的兼容性。使用 CGFloat 可以让程序忽略具体的硬件。不过现在越来越多的硬件都支持 Double 了。

Swift 没有办法自动推断出 CGFloat 类型,所以必须要显式声明。

(3)给按钮增加缩放效果

给按钮增加下面的修饰

.scaleEffect(animationAmount)

scaleEffect 修饰是以给定的数字改变视图的尺寸(水平和垂直同步修改,同时和锚点关联)。数字的含义如下

  • 符号:正数,原始样子;负数,上下翻转
  • 绝对值:大于 1,放大;0 ~ 1,缩小;0:消失

(4)增加按钮动作

在按钮的动作响应代码中,添加下面的代码

self.animationAmount += 1

这样每次点击按钮时,按钮就变大一些。因为不会以新的尺寸重新渲染,所以会变得有点模糊。

(5)增加动画

给按钮增加 animation 修饰

.animation(.default)

这样,我们能看到变大的过程是平缓的,不是直接切换的。现在是默认效果。

(6)增加模糊效果

给按钮增加模糊修饰 blur,其中的参数表示模糊半径,最小为 0(不模糊),负数无意义。

.blur(radius: (animationAmount - 1) * 3)

现在放大和模糊的效果会在点击按钮后同时出现了。我们没有告诉程序关于动画的任何细节(起止时间,关键帧等),但是已经可以看见动画了。

3. Customizing animations in SwiftUI(在 SwiftUI 中自定义动画)

本段内容,我觉得课程仍然使用那个圆形按钮,效果不明显,所以我改成了方块的水平运动(这里有个 offset 修饰可能是没介绍过,这个修饰使用的数据类型也是 CGFloat)

(1)动画形式

刚才使用的 .animation(.default)是默认动画,实际上是“缓入缓出”动画,即 easeInOut,类似的还有 easeIn,easeOut。还有弹簧动画:

.animation(.interpolatingSpring(stiffness: 50, damping: 1))

刚度 stiffness 会影响动画的初始速度,阻尼 damping 会影响来回弹跳的时间。具体还需要大家自己测试摸索。

(2)时间控制

持续时间 duration 和延迟 delay,数值都是 Double,单位是秒

(3)重复动画

.repeatCount(3, autoreverses: true)		// 有限重复,数字是重复次数,autoreverses是否复位
.repeatForever(autoreverses: true)		// 无限重复

下面是 CustomAnimation 的主体部分

struct CustomAnimation: View {
    @State private var animationAmount: CGFloat = 1
    @State private var offsetAmount: CGFloat = 0
    var body: some View {
        VStack{
            HStack {
                RoundedRectangle(cornerRadius: 10)
                .frame(width: 60, height: 40)
                .foregroundColor(.blue)
                .offset(x: offsetAmount)
                .animation(
                    Animation.easeInOut(duration: 2)        // 动画形式和持续时间
                    .delay(1)                               // 延迟时间
                    .repeatCount(2, autoreverses: true)     // 重复次数,是否复位
                    .repeatForever(autoreverses: false)     // 连续动画
                )
                .onAppear{
                    self.offsetAmount = 280
                }
            }
            .padding(.horizontal, 15)
            .frame(maxWidth: .infinity, alignment: .leading)
            .frame(height: 100)
            .background(Color(.gray))    
            // 后面还有三个 HStack,分别注释了 delay 并修改了动画形式
        }
    }
}

(4)一个有脉动效果的按钮

使用 overlay 修饰,在按钮上覆盖一个圆形。在圆形上使用stroke修饰进行描边,使用 scaleEffect 和 opacity 修饰缩放比例和透明度并使之随 animationAmount 数值改变而改变,使用 animation 修饰定义动画并在动画上使用 repeatForever 修饰重复动画,就得到了一个有脉动效果的按钮。

Button("点我") {
    // self.animationAmount += 1
}
.padding(40)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
.overlay(
    Circle()
        .stroke(Color.red)
        .scaleEffect(animationAmount)
        .opacity(Double(2 - animationAmount))
        .animation(
            Animation.easeOut(duration: 1)
                .repeatForever(autoreverses: false)
        )
)
.onAppear {
    self.animationAmount = 2
}

4. Animating bindings(动画绑定)

对于修饰器参数,感觉除去布尔值,几乎所有值的变化都可以制作动画。先看下面的代码

struct ContentView: View {
    @State private var animationAmount: CGFloat = 1

    var body: some View {
        VStack {
            Stepper("缩放系数(1~10):\(animationAmount, specifier:"%g")", 
                    value: $animationAmount.animation(), in: 1...10)
                .padding()

            Spacer()

            Button("点我") {
                self.animationAmount += 1
            }
            .padding(40)
            .background(Color.red)
            .foregroundColor(.white)
            .clipShape(Circle())
            .scaleEffect(animationAmount)
        }
    }
}

其中的 Stepper 就是绑定了 $animationAmount.animation() ,所以点击加减按钮的时候会有动画变化。而按钮只是通过在点击时改变 animationAmount 的值来影响 scaleEffect 修饰去改变按钮的缩放,并未定义值变化的动画,所以没有动画显示。

绑定在 Stepper 上的动画就是隐式动画,绑定动画同样可以使用动画修饰器,如动画形式、时长、延迟、重复什么的。绑定动画不是在视图上设定动画和通过状态变量显式定义如何动画。显式动画中的状态变量不知道触发了动画,绑定动画则是视图不知道自己会动画。二者皆有效,二者皆重要。

大叔的小实验

下面的代码是测试两个按钮通过切换 布尔值 状态,分别利用透明度和缩放比例来控制控件是否显示,状态的切换使用动画。实验中发现 CGFloat 从 0 到 1 程序不执行了,改成 0.0001 或者更小(反正能保证看不见就行)就没事了。

@State private var show1 = false
var title1: String {
    return show1 ? "解散" : "集合"
}
var opacity1: Double {
    return show1 ? 1 : 0
}

@State private var show2 = false
var title2: String {
    return show2 ? "解散" : "集合"
}
var scale2: CGFloat {
    return show2 ? 1 : 0.00001		// 设成 0 程序就停住了,我猜是 CGFloat 的事情
}

var body: some View {
    VStack {
        HStack {
            Button(action: {
                self.show1.toggle()
            }) {
                Text(title1)
        	}

            RoundedRectangle(cornerRadius: 10)
            .frame(width: 60, height: 40)
            .foregroundColor(.blue)
            .opacity(opacity1)
            .animation(Animation.easeInOut(duration: 1))
        }
        
        HStack {
            Button(action: {
                self.show2.toggle()
            }) {
                Text(title2)
            }

            RoundedRectangle(cornerRadius: 10)
            .frame(width: 60, height: 40)
            .foregroundColor(.green)
            .scaleEffect(scale2)
            .animation(Animation.easeInOut(duration: 1))
        }    
    }.padding()
}

5. Creating explicit animations(创建显式动画)

前面已经了解了 SwiftUI 如何通过将animation()修饰符附加到视图来创建隐式动画,以及如何通过将animation()修饰符添加到绑定来创建动画的绑定更改,但是还有第三种方式可以创建动画:在状态发生改变后,显式地让 SwiftUI 产生动画变化。

我们仍然不必手动创建动画的每一帧,那是 SwiftUI 的活儿,它通过状态更改前后查看视图的状态持续地找出动画。

但是,现在,我们明确地希望在状态发生任意改变时发生动画:它没有附加到绑定,也没有附加到视图,只是我们明确要求发生特定的动画,因为状态变化。

为了说明这一点,让我们再次回到简单的按钮示例:

struct ContentView: View {   
    var body: some View {
        Button("点我") {
            // 点击后的动作
        }
        .padding(50)
        .background(Color.red)
        .foregroundColor(.white)
        .clipShape(Circle())
    }
}

现在要在点击该按钮后,让按钮带有3D效果旋转。这需要另一个新的修饰器,rotation3DEffect()可以为其指定以度为单位的旋转量以及确定视图旋转方式的轴。共有三个维度的轴:

  • X轴(水平):可以想象成屏幕的上边,考虑原点的话,或许就先想成上沿,左边是0。
  • Y轴(垂直):可以想象成屏幕的左边。上面是 0。
  • Z轴(深度):即通过左上角做屏幕的垂线,屏幕的上层是正数。

首先需要一个可以修改的某些状态,并且旋转度指定为Double

@State private var animationAmount = 0.0 	// 除去 Int 外,我觉得应该尽量显式声明类型

接下来,让按钮animationAmount沿其 Y 轴旋转:

.rotation3DEffect(.degrees(animationAmount), axis: (x: 0, y: 1, z: 0))

现在是重要部分:在按钮的动作中添加一些代码,以便在animationAmount每次点击时将其添加360 。

如果只写self.animationAmount += 360,那么更改将立即发生,因为按钮上没有附加动画修改器。这是显式动画出现的方式。如果使用withAnimation()闭包,那么SwiftUI将确保由新状态引起的任何更改都将自动进行动画处理。

因此,现在将其放入按钮的操作中:

withAnimation {
    self.animationAmount += 360
}

现在运行,每次点击按钮,它就会在3D空间中旋转。可以自己尝试一下其他轴或者多个轴。

withAnimation()同样可以使用可以在SwiftUI中其他位置使用的所有相同动画作为动画参数。例如,

withAnimation(.interpolatingSpring(stiffness: 5, damping: 1)) {
    self.animationAmount += 360
}

这样就使用上了弹簧动画。

你可能感兴趣的:(#,第,3,阶段(Days,26-35):项目,4-6)