从iOS 15.0开始,苹果废弃了之前的.animation(Animation?)
,建议开发者使用.animation(Animation?, value: Equatable)
或者withAnimation
替代。
个人感觉改版之后虽然可能有些不习惯,但是开发的可能性和自由度更大了。
但是在讲二者区别之前,我们需要了解一下UI动画。如果你了解这部分可以跳过。
何为动画
动画是由各种位移、颜色变化、大小变化等属性变化的过程。
具体到SwiftUI的View,就是它们的.offset
、.foregroundColor
、.frame
等属性逐渐变化的时候产生的效果。
但是如果你直接改变变量,属性会直接变化,非常生硬,这不能被称为动画。苹果提供了非常简单的实现过度动画的办法,那就是.animation
。
可以理解成我们设定好关键帧,然后设置中间帧就可以做出动画啦!
不同样式的过度动画(中间帧)
苹果提供了很简单的方式,但是或许是太简单了,就没有人详细说说这个。
首先动画运动节奏也分很多种,例如缓进缓出、匀速运动等,所以这里介绍一下苹果提供的几种模式(如果是nil
就是无动画):
具体的代码后面会列出来,因为有一个特性这里还没提,所以这里先不列出代码。
新旧版本不同之处
现在我们具体说明一下.animation(Animation?,)
和.animation(Animation?, value: Equatable)
二者之间的不同之处。
老版的.animation(Animation?)
是放在需要运动或者改变的View下面,当View改变的时候,出现过渡动画,过渡动画包括运动、样式、颜色等属性。
新版.animation(Animation?, value: Equatable)
比老版多了一个参数,那就是value
。
这个参数value
的作用是当这个值发生改变的时候,才会出现应用我们指定的过度动画,过渡动画也包括运动、样式、颜色等属性。如果View的其他属性发生变化,则不会出现动画。
所以上面那个动画的代码如下:
struct AnimationView: View {
@State private var offsetX: CGFloat = 150
var body: some View {
VStack {
VStack {
Text(".default(默认)")
Rectangle()
.frame(width: 30, height: 30)
.offset(x: offsetX)
.foregroundColor(Color.pink)
/* 如果是`nil`就是无动画 */
.animation(.default, value: offsetX)
Text(".linear(匀速)")
Rectangle()
.frame(width: 30, height: 30)
.offset(x: offsetX)
.foregroundColor(Color.pink)
.animation(.linear, value: offsetX)
Text(".easeIn(开头缓慢,逐渐加速)")
Rectangle()
.frame(width: 30, height: 30)
.offset(x: offsetX)
.foregroundColor(Color.pink)
.animation(.easeIn, value: offsetX)
Text(".easeOut(结尾逐渐减速,缓慢结束)")
Rectangle()
.frame(width: 30, height: 30)
.offset(x: offsetX)
.foregroundColor(Color.pink)
.animation(.easeOut, value: offsetX)
Text(".easeInOut(缓出缓进,上面的融合体)")
Rectangle()
.frame(width: 30, height: 30)
.offset(x: offsetX)
.foregroundColor(Color.pink)
.animation(.easeInOut, value: offsetX)
}
Button(action: {
offsetX = -150
}, label: {
Text("开始运动")
.padding()
.overlay(
RoundedRectangle(cornerRadius: 15)
.stroke(lineWidth: 3)
)
})
}
}
}
但是由于这个value
,我们只能当这个值发生变化的时候才能有动画,那么如果我们需要在观察到几个值当中的某一个发生变化就有动画的话,那该怎么办呢?
如果你要观察的值是同一个类型的话,那么可以使用数组,例如[offsetX, offsetY]
,这样的话就相当于观察这个数组有没有变化了,只要有一个值发生变化,那么数组就会变化。这样可以达到我们需要的效果。
需要观察多个值该怎么办
如果需要观察的值不是同一个类型的,例如我们要观察位移和颜色,一个是CGFloat
类型,一个是Color
类型。这样就很麻烦了。所以这里列举有几个解决方案,以防在某种情况下,某种方法会更加麻烦:
第一种:
可以搞个数据结构,然后观察数据。或许某种情况你用这种方法很方便,
首先新建一个结构体,协议是Equatable
,如下:
struct Test: Equatable {
var offsetX: CGFloat
var color: Color
}
然后是View部分:
.animation(.default, value: Test(offsetX: offsetX, color: color))
这种方法相对来说通用一些,而且可以观察多次变化,就是有点麻烦。
第二种:
由于value
是符合Equatable
协议的,也就是说这里的value
其实是个Bool
值,我们虽然写作value: color
这种,但是实际上它是在判断这个值有没有发生变化,相当于color == 之前的color
,通过这个来返回一个Bool
值(很经典的C语言编程技巧,如果你熟悉C语言应该很容易就明白了)。
但是我们不能直接放个Bool
值在这,就想它必须出动画或者不出动画,那是不会的。它是必须要判断这个值有没有发生变化的。
所以我们可以写成这样:
.animation(.default, value: offsetX == 150 && color == Color.pink)
这种方法的写法相对简单,但是由于只对比一个值,所以某些情况下可能不好用。
动画时间长度
我们可以设置过渡动画的时长,来控制动画的快慢和节奏,这个很简单。老版本的可能很多人都会用,但是新版本由于官方没提,网上也没人说,所以这里讲一下。
在动画样式的后面加个(duration: 时长)
即可,时长的单位是秒。
//这里表示动画时长为3秒
.animation(.linear(duration: 3), value: offsetX)
总结
看到这里你应该就可以做出简单的动画啦,不过我还会写一些相关的概念、技巧的博客,来帮助需要的人。
希望能帮到有需要的人~