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因素,換成字典的方式,比較不容易把自己繞暈。