SwiftUI——iOS15新版.animation制作动画介绍

2,960 阅读5分钟

苹果的动画效果可以说是行业标杆了,作为苹果平台的开发者自然也不能拉下。所以会用几篇文章来教各位如何在SwiftUI中做出常见的简单动画。

本篇文章是SwiftUI中关于动画的基础知识。

但是在讲具体之前,我们需要了解一下动画的概念,这样帮助你更好的理解和吸收。如果你了解这部分可以跳过。

何为动画

动画是一系列图像(也被称为帧)快速播放,再通过视觉暂留现象在人眼中形成的流畅运动。早期的动画、电影特效都是需要一帧一帧去做的。

但是在SwiftUI中,动画的本质是一个View的位移、颜色、大小、形状等属性发生变化的过程。再具体点,就是.offset.foregroundColor.frame等属性逐渐变化的过程。我们只需要设定开始的样式和结束时的样式,还有中间过渡的样式就可以了(这就是本篇关键点.animation,它会自动生成中间的过渡帧)。

如果你有动画或者视频经验,那么可以理解成我们设定好关键帧,然后设置中间帧的样式就可以做出动画啦!

不同样式的过渡动画(中间帧)

这里列举一下过渡动画的样式,例如缓进缓出、匀速运动等:

如果是nil就是无动画(这个也可以做出来一些效果),如下:

具体的代码后面会列出来,因为有一个需要注意的点,所以这里先不列出代码。

新旧版本不同之处

如果你在网上搜如何做动画,得到的实现方法可能和我这里写的不太一样。这是因为从iOS 15.0开始,苹果废弃了之前的.animation(Animation?),建议开发者使用新的.animation(Animation?, value: Equatable)或者withAnimation方法。

现在我们具体说明一下.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)

总结

看到这里你应该就可以做出简单的位移、渐变动画啦,可以自己动手试试看。