Animations and States
终于来到了SwiftUI的重头戏了,这篇主要内容是动画和状态,重中之重是状态。
视频教程地址:教程链接
SwiftUI整体上是一个MVVM的框架了,有别于微软的MVVM框架,Android的LiveData,LifeCycle,DataBinding,Flutter的Stateful这一套概念,SwiftUI的MVVM更加的简便,封装程度更高,类似于web上的vue搞的这一套。
微软基于.net平台的MVVM框架有prism,MVVNLight等,他们在属性改变的时候都需要手动编写代码来通知界面刷新。
Android这一套MVVM框架简直堪称反面教材,这一套东西看下来能把人绕晕。
Flutter的状态机制,要首先写一个类继承StatefulWidget,然后再写一个类继承State的泛型类,总之也是挺麻烦的。
而SwiftUI只需要在需要改变的状态上加一个@State的注解,就能在状态改变的时候立即刷新界面,相当易用。
状态的使用
这个地方我们需要创建一个效果,就是点击卡片之后,旋转角度变成0,再点一下,则恢复旋转。
按照以前的MVC的做法,给卡片添加一个点击事件监听器,在监听器中判断是否旋转卡片,然后用旋转方法,来改变卡片的角度。
现在MVVM框架的做法是,设置一个状态,这个状态在View层面跟卡片的旋转角度绑定,然后在后台又跟点击事件关联在一起,点击的时候,状态发生改变,就会自动通知卡片的旋转角度发生变化。
首先在ContentView结构体内创建一个全局布尔类型变量show,并且在前面用 @State注解 修饰:
struct ContentView: View {
@State var show = false
var body: some View {
...
}
}
这样就创建了一个是否展示卡片的状态。
然后给CardView卡片写一个点击事件的回调,这里用到onTapGesture这个modifier:
CardView()
.blendMode(.hardLight)
.onTapGesture {
show.toggle()
}
toggle这个函数的作用就是切换真值,即true变false,false变true。
在XCode11中,show.toggle() 这句代码必须前面加上范围限定self.才能正常编译。到了XCode12中,已经不需要再这样操作。
这一步就是上面说到的把状态和点击事件关联起来,接下来要做的就是把状态和旋转角度绑定,这一步也很简单,只需要一个 三目运算符 :
BackCardView()
.background(Color("card3"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: -20)
.scaleEffect(0.95)
.rotationEffect(.degrees(show ? 0 : 5))
.rotation3DEffect(
.degrees(5),
axis: (x: 10, y: 1.0, z: 0.0)
)
.blendMode(.hardLight)
show ? 0 : 5 这一个三目表达式,就将状态和旋转角度绑定了起来。
点击预览界面上的三角形,点击一下可以看到效果:
同理,将这个state和卡片的偏移值(offset) 绑定起来,则状态改变时,会同时影响旋转角度和偏移值:
BackCardView()
.background(Color("card3"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: show ? -200 : -20)
.scaleEffect(0.95)
.rotationEffect(.degrees(show ? 0 : 5))
.rotation3DEffect(
.degrees(5),
axis: (x: 10, y: 1.0, z: 0.0)
)
.blendMode(.hardLight)
效果是这样的:
动画的使用
上面状态刷新的时候,界面变化挺突兀的,有一些渐变的动画是最好不过的,这个时候要用到动画(animation):
BackCardView()
.background(Color("card3"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: show ? -200 : -20)
.scaleEffect(0.95)
.rotationEffect(.degrees(show ? 0 : 5))
.rotation3DEffect(
.degrees(5),
axis: (x: 10, y: 1.0, z: 0.0)
)
.blendMode(.hardLight)
.animation(.easeInOut(duration: 0.3))
这里的easeInOut是指定了动画的一个interpolator(插值器),影响的是动画的快慢节奏,类似于Android动画里面的插值器,有线性插值器,先快后慢插值器,先慢后快插值器,等等。这里的easeInOut指的是一种开头和结束都比较缓慢,中间比较快的插值器。
效果是这样的:
当然这里的duration只用了0.3秒,看不太出来速率的变化,如果把时间拉长,速率的变化就会比较明显了。
同理,把这张卡片上用到的动画全部应用于另外一张背景卡片:
BackCardView()
.background(Color("card4"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: show ? -400 : -40)
.scaleEffect(0.9)
.rotationEffect(.degrees(show ? 0 : 10))
.rotation3DEffect(
.degrees(5),
axis: (x: 10, y: 1.0, z: 0.0)
)
.blendMode(.hardLight)
.animation(.easeInOut(duration: 0.5))
BackCardView()
.background(Color("card3"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: show ? -200 : -20)
.scaleEffect(0.95)
.rotationEffect(.degrees(show ? 0 : 5))
.rotation3DEffect(
.degrees(5),
axis: (x: 10, y: 1.0, z: 0.0)
)
.blendMode(.hardLight)
.animation(.easeInOut(duration: 0.5))
这里把动画时长改成了0.5s,效果:
同理,状态也可以绑定颜色,状态改变并应用动画时,颜色会在两种颜色之间变化:
BackCardView()
.background(show ? Color("card4") : Color("card3"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: show ? -200 : -20)
.scaleEffect(0.95)
.rotationEffect(.degrees(show ? 0 : 5))
.rotation3DEffect(
.degrees(5),
axis: (x: 10, y: 1.0, z: 0.0)
)
.blendMode(.hardLight)
.animation(.easeInOut(duration: 0.5))
效果:
同样把颜色的动画应用于另外一张卡片:
BackCardView()
.background(show ? Color("card3") : Color("card4"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: show ? -400 : -40)
.scaleEffect(0.9)
.rotationEffect(.degrees(show ? 0 : 10))
.rotation3DEffect(
.degrees(5),
axis: (x: 10, y: 1.0, z: 0.0)
)
.blendMode(.hardLight)
.animation(.easeInOut(duration: 0.5))
BackCardView()
.background(show ? Color("card4") : Color("card3"))
.cornerRadius(20)
.shadow(radius: 20)
.offset(x: 0, y: show ? -200 : -20)
.scaleEffect(0.95)
.rotationEffect(.degrees(show ? 0 : 5))
.rotation3DEffect(
.degrees(5),
axis: (x: 10, y: 1.0, z: 0.0)
)
.blendMode(.hardLight)
.animation(.easeInOut(duration: 0.5))
效果:
当然上一篇中最后用到的高斯模糊(blur) 也可以用同样的手段加上动画:
TitleView()
.blur(radius: show ? 20 : 0)
.animation(.easeInOut(duration: 0.3))
BottomCardView()
.blur(radius: show ? 20 : 0)
.animation(.easeInOut(duration: 0.3))
效果:
下一篇是手势动作,和事件。