SwiftUI 一種旋轉木馬效果的實現(Carousel)
阿新 • • 發佈:2022-05-19
Demo實現原理:
首先確定 5 個位置資訊,移動只是把5個位置資訊重新設定到5個方塊,把賦值語句放到withAnimation { } 中,即可產生動畫位移動畫效果。其中麻煩的是zIndex。
確定位置資訊
為了方便實現原理,這裡使用硬編碼的方式,實際專案,根據專案求算位置資訊。
struct HomeCarousel: View { struct Slot{ var id: String var scale: CGFloat var offsetX: CGFloat } struct Cell: Identifiable { var id: String var color: Color } let standarCellSize = CGSize(width: 109, height: 330) let Slots: [Slot] = [ Slot(id: "left",scale: 0.6, offsetX: -196.2), Slot(id: "left1",scale: 0.8, offsetX: -109), Slot(id: "middle",scale: 1, offsetX: 0), Slot(id: "right1",scale: 0.8, offsetX: 109), Slot(id: "right",scale: 0.6, offsetX: 196.2) ] let myCells: [Cell] = [ Cell(id: "hanfu0", color: .yellow), Cell(id: "hanfu1", color: .red), Cell(id: "hanfu2", color: .green), Cell(id: "hanfu3", color: .blue), Cell(id: "hanfu4", color: .gray), ] @State var cellSlotDic: Dictionary<String, Slot> @State var cellZIndexDic: Dictionary<String,Double> init() { _cellSlotDic = State(initialValue: [ "hanfu0": Slots[0], "hanfu1": Slots[1], "hanfu2": Slots[2], "hanfu3": Slots[3], "hanfu4": Slots[4], ]) _cellZIndexDic = State(initialValue: [ "hanfu0": 2.0, "hanfu1": 2.0, "hanfu2": 2.0, "hanfu3": 2.0, "hanfu4": 2.0, ]) } }
上述程式碼用key: value的方式,把view和 Slot 、index對應起來。
顯示view
根據cell.id從字典中取出渲染數值.
var body: some View { ZStack { ForEach(myCells) { v in Rectangle() .overlay( Text(v.id) .foregroundColor(.white) ) .foregroundColor(v.color) .frame(width: standarCellSize.width, height: standarCellSize.height) .scaleEffect(cellSlotDic[v.id]!.scale) .offset(x: cellSlotDic[v.id]!.offsetX) .zIndex(cellZIndexDic[v.id]!) } HStack { Button("<<<") { withAnimation { reorderValues(.scrollToRight) } } Button(">>>") { withAnimation { reorderValues(.scrollToLeft) } } } .background(Color.green) .offset(y: 250) .zIndex(20) } }
改變位置資訊
原理解析:現有位置是:
Slots[0] | Slots[1] | Slots[2] | Slots[3] | Slots[4] |
---|---|---|---|---|
hanfu0 | hanfu1 | hanfu2 | hanfu3 | hanfu4 |
當向左滾動時,最左個被擠出螢幕,回到最右的位置,此時的位置資訊應該為:
Slots[0] | Slots[1] | Slots[2] | Slots[3] | Slots[4] |
---|---|---|---|---|
hanfu1 | hanfu2 | hanfu3 | hanfu4 | hanfu0 |
注意,記得把hanfu0 的zIndex設定為最小,這樣才能實現從背後繞過的效果。
enum ReorderMode { case scrollToLeft case scrollToRight } func reorderValues(_ mode: ReorderMode) { var keys:[String] { myCells.map({$0.id}) } switch mode { case .scrollToLeft: var newVRects = cellSlotDic var newZIndexs = cellZIndexDic for i in 0..<keys.count { let preI = i - 1 < 0 ? keys.count-1 : i - 1 let rect = cellSlotDic[keys[i]]! newVRects[keys[preI]] = rect // 注意,值的改變是立即生效的,只是zIndex不會有動畫,而位移和形變會有動畫。 // 所以,經過以上的重新排序,一旦給賦值vRects,在檢視的渲染中,原本的最左已經跑到最右,為了讓目標cell播放位移動畫時,是從後邊繞過的,因此設定最小的zIndex let theRightmostKey = rect.id == Slots.last!.id newZIndexs[keys[i]] = theRightmostKey ? 1 : 3 } cellZIndexDic = newZIndexs cellSlotDic = newVRects case .scrollToRight: var newVRects = cellSlotDic var newZIndexs = cellZIndexDic for i in 0..<keys.count { let nextI = i + 1 > keys.count-1 ? 0 : i + 1 let rect = cellSlotDic[keys[i]]! newVRects[keys[nextI]] = rect let theLeftmostKey = rect.id == Slots.first!.id newZIndexs[keys[i]] = theLeftmostKey ? 1 : 3 } cellZIndexDic = newZIndexs cellSlotDic = newVRects } }
題外話
字典的資料互換,可不簡潔。
一開始是使用陣列對應每個位置,實現陣列元素“平移”會比字典容易許多,程式碼也比上面簡潔許多。
因為加入zIndex因素,換成字典的方式,比較不容易把自己繞暈。