课程链接:https://www.bilibili.com/video/BV1q64y1d7x5?p=8
课程项目仓库:https://github.com/cnatom/MemorizeSwiftUI
这节课老师全程在写代码,无法按照课程从头到尾总结。
因此从其中精炼出最主要的几个知识点,各自以实例说明
自定义翻转卡片:插值动画Animatable & 三维旋转.rotation3DEffect
插值动画可以精准调控View在动画过程中每一帧的表现,只需要继承Animatable 并实现其中的animatableData 变量即可。
我们通过自定义一个能点击翻转的卡片View来说明其作用。
- 如下代码,我们定义了一个CardView,接受一个参数show,当show变化时,卡片会在正面与反面之间翻转。
struct ContentView: View {
@State var show = false
var body: some View {
CardView(show: show)
.onTapGesture {
withAnimation(.linear(duration: 3)) {
self.show = !self.show
}
}
}
}
- 我们先实现一个没有翻转动画的CardView
struct CardView: View{
var show: Bool
let shape = RoundedRectangle(cornerRadius: 16)
var body: some View {
ZStack{
if show {
shape.fill().foregroundColor(.white)
shape.strokeBorder(lineWidth: 5)
Text("😀")
} else {
shape.fill()
}
}
.foregroundColor(.blue)
.frame(width: 100, height: 150)
}
}
点击效果如下:
此处的渐变效果来自于withAnimation 显式动画。
- 之后,我们为
CardView 添加3D翻转效果rotation3DEffect ,让卡片在0度到180度之间绕着Y轴翻转
此处 axis: (0,1,0) 代表绕Y轴旋转
同样的,绕X轴:axis:(1,0,0),绕Z轴:axis:(0,0,1)
struct CardView: View{
...
var body: some View {
ZStack{
...
}
...
.rotation3DEffect(Angle(degrees: show ? 0 :180), axis: (0,1,0))
}
}
效果如下:
emmm,虽然翻转了,但是卡片内容是渐变过去的,而不是在90度的时候突然从正面切换到反面。
所以,我们可以设置一个角度变量。
- 当0->90度时,显示卡片正面
- 当90->180度时,显示卡片反面
要完成这种精细的操作,就需要借助SwiftUI提供的协议:Animatable
- 优化翻转动画,使翻转角度为90时让View“突变”
首先,声明要实现Animatable 协议
struct CardView: View, Animatable{
...
}
然后,设置一个变量,用来记录当前旋转的角度。并将var show: Bool 替换为构造函数,传入true时为0度,false时为180度
struct CardView: View, Animatable{
init(show: Bool){
rotation = show ? 0 : 180
}
var rotation: Double
...
}
再实现Animatable 协议中的animatableData 变量,并将其与rotation 关联。
这里说明一下原理,
- 如果不实现
animatableData ,那么从正面翻转时,rotation的值是“突变”的,从0->180,就无法在90度的时候让卡片突变。 - 实现
animatableData 并将其与rotation 关联,那么rotation的值是渐变的,0->1->2->3->…->180,从而实现对每一角度的调整。
struct CardView: View, Animatable{
...
var rotation: Double
var animatableData: Double{
get { rotation }
set { rotation = newValue }
}
...
}
最后,在body中添加if rotation < 90 ,以实现在90度时的“突变”
struct CardView: View, Animatable{
...
var body: some View {
ZStack{
if rotation < 90 {
...
} else {
...
}
}
...
.rotation3DEffect(Angle(degrees: rotation), axis: (0,1,0))
}
}
最终效果如下:
🎉🎉🎉🎉🎉🎉🎉干得漂亮
以下为全部代码:
struct ContentView: View {
@State var show = false
var body: some View {
CardView(show: show)
.onTapGesture {
withAnimation(.linear(duration: 4)) {
self.show = !self.show
}
}
}
}
struct CardView: View, Animatable{
init(show: Bool){
rotation = show ? 0 : 180
}
var rotation: Double
var animatableData: Double{
get { rotation }
set { rotation = newValue }
}
let shape = RoundedRectangle(cornerRadius: 16)
var body: some View {
ZStack{
if rotation < 90 {
shape.fill().foregroundColor(.white)
shape.strokeBorder(lineWidth: 5)
Text("😀")
} else {
shape.fill()
}
}
.foregroundColor(.blue)
.frame(width: 100, height: 150)
.rotation3DEffect(Angle(degrees: rotation), axis: (0,1,0))
}
}
几何匹配.matchedGeometryEffect
使用.matchedGeometryEffect 可以实现View位置的移动。
我们通过一个样例来说明
struct ContentView: View {
@State var go = false
var body: some View {
Group {
if go {
VStack {
CardView()
Spacer()
}
} else {
VStack {
Spacer()
CardView()
}
}
}
.onTapGesture {
withAnimation {
go.toggle()
}
}
}
}
效果如下:
之后给两个CardView添加.matchedGeometryEffect 即可。
struct ContentView: View {
@State var go = false
@Namespace var cardNameSpace
var body: some View {
Group {
if go {
VStack {
CardView()
.matchedGeometryEffect(id: "mycard", in: cardNameSpace)
Spacer()
}
} else {
VStack {
Spacer()
CardView()
.matchedGeometryEffect(id: "mycard", in: cardNameSpace)
}
}
}
.onTapGesture {
withAnimation {
go.toggle()
}
}
}
}
只有两个id相同才会有位置移动效果
效果如下:
|