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。