Animation Video Record
Animation Data Flow
struct Block: Identifiable, CustomStringConvertible {
typealias AtPosition = (Int, Int)
let id: Int
var number: Int
var at: AtPosition
func getIndex(gridRows: Int) -> Int { self.at.0 * gridRows + self.at.1 }
var description: String {
return "BLOCK(id: \\(id), number: \\(number), at: (\\(at.0), \\(at.1)))"
}
}
每一个小方块都是Identifiable
,这使得即使BlockGridView
组件发生更新,每个子方块都有id标志,不会销毁进行重渲染,于react里面的map id道理一样。
.transition(
.asymmetric(
insertion: .scale(scale: 0, anchor: UnitPoint(x: offset.x / blockSize + 0.5, y: offset.y / blockSize + 0.5))
.animation(.linear(duration: 0.4).delay(0.3)),
removal: .opacity.animation(nil)
)
)
入场动画用transition
,当block出现时,会调用asymmetric
insertion
定义的动画,因为block是绝对布局,如果.scale
不指定anchor
的话,默认会从绝对布局占用的位置中间开始动画,也就是BlockGridView
的center,所以要加上anchor
,使得中心点到小方块的中心,另外transition
里面的AnyTransition
方法之后都需要指定.animation
,这样入场离场动画才能正确进行,入场动画需要等block位移完成之后再进行,所以添加了.delay(0.3)
。因为不需要离场动画,所以removal
的.animation
参数设置为nil。这样一来,当有新的block出现时(新的id),便会执行入场动画。
createBlockView(number: block.number, at: block.at)
.zIndex(Double(block.number))
.animation(.linear(duration: 0.3), value: block.getIndex(gridRows: gridRows))
当Block的位置发生改变时,block.at
会更新,而block.getIndex()
与block.at
是一一对应的,所以block.at
改变时,会触发.animation
方法,而block.at
会引起Block
的frame offset
变化,这个swiftUI已经有定义对应的动画效果,所以当block.at
更新时会产生位移动画。
struct BlockViewWithAnimation: View {
var number: Int?
var blockSize: CGFloat
@State private var numberDisplay: Int?
@State private var opacity: Double = 1
@State private var scale: CGFloat = 1
init(number: Int? = nil, blockSize: CGFloat) {
self.number = number
self.blockSize = blockSize
}
var body: some View {
BlockView(number: numberDisplay, blockSize: blockSize, textOpacity: opacity)
.scaleEffect(scale)
.onChange(of: number, perform: { value in
let task1 = DispatchWorkItem {
numberDisplay = value
opacity = 0
scale = 1
withAnimation(.linear(duration: 0.2)) {
opacity = 1
scale = 1.2
}
}
let task2 = DispatchWorkItem {
withAnimation(.linear(duration: 0.2)) {
scale = 1
}
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3, execute: task1)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: task2)
})
.onAppear(perform: {
numberDisplay = number
})
}
}
当Block
的number发生改变时,需要等位移动画完成之后再进行合并动画,所以调用了DispatchQueue.main.asyncAfter
方法,延迟0.3秒再执行动画。当位移结束后,才将显示的数字更改为合并后的数字,并且此时的opcity为0,scale也从1准备变化。在0.2秒内Block
从opacity = 0, scale = 1
变化为opacity = 1, scale = 1.2
,这个过程结束后Block再在0.2秒内从scale = 1.2
变化为scale = 1
。