Animation Video Record

Animation Video Record

Animation Data Flow

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会引起Blockframe 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秒内Blockopacity = 0, scale = 1变化为opacity = 1, scale = 1.2,这个过程结束后Block再在0.2秒内从scale = 1.2变化为scale = 1

GitHub - 13773753970/Game-2048