SwiftUI卡片轮播:实现点击卡片滑动至屏幕中央
实现点击卡片滑动至中央的效果
要实现点击任意卡片使其平滑滑动到屏幕中央的效果,你需要对现有代码做两处关键修改:
- 将点击手势从整体的
ZStack移至每个卡片视图上,这样才能识别具体被点击的卡片。 - 在点击手势的处理逻辑中,计算当前中心卡片到目标卡片的最短路径,并通过动画更新滑动状态。
以下是修改后的完整代码:
import SwiftUI struct Item: Identifiable { var id: Int var title: String var color: Color } class Store: ObservableObject { @Published var items: [Item] let colors: [Color] = [.red, .orange, .blue, .teal, .mint, .green, .gray, .indigo, .black] // 模拟数据 init() { items = [] for i in 0...7 { let new = Item(id: i, title: "Item \(i)", color: colors[i]) items.append(new) } } } struct ContentView: View { @StateObject var store = Store() @State private var snappedItem = 0.0 @State private var draggingItem = 0.0 @State var activeIndex: Int = 0 var body: some View { ZStack { ForEach(store.items) { item in // 卡片视图 ZStack { RoundedRectangle(cornerRadius: 18) .fill(item.color) Text(item.title) .padding() } .frame(width: 200, height: 200) .scaleEffect(1.0 - abs(distance(item.id)) * 0.2 ) .opacity(1.0 - abs(distance(item.id)) * 0.3 ) .offset(x: myXOffset(item.id), y: 0) .zIndex(1.0 - abs(distance(item.id)) * 0.1) .onTapGesture { withAnimation { let count = Double(store.items.count) let current = snappedItem let target = Double(item.id) // 计算最短路径的偏移量 var delta = target - current delta = delta.remainder(dividingBy: count) if delta > count / 2 { delta -= count } else if delta < -count / 2 { delta += count } // 更新滑动状态,实现平滑动画 draggingItem = current + delta snappedItem = target.remainder(dividingBy: count) activeIndex = item.id } } } } .gesture(getDragGesture()) } private func getDragGesture() -> some Gesture { DragGesture() .onChanged { value in draggingItem = snappedItem + value.translation.width / 100 } .onEnded { value in withAnimation { draggingItem = snappedItem + value.predictedEndTranslation.width / 100 draggingItem = round(draggingItem).remainder(dividingBy: Double(store.items.count)) snappedItem = draggingItem // 更新当前激活的卡片索引 activeIndex = Int(draggingItem) if activeIndex < 0 { activeIndex = store.items.count + activeIndex } } } } func distance(_ item: Int) -> Double { return (draggingItem - Double(item)).remainder(dividingBy: Double(store.items.count)) } func myXOffset(_ item: Int) -> Double { let angle = Double.pi * 2 / Double(store.items.count) * distance(item) return sin(angle) * 200 } }
关键修改说明:
- 点击手势绑定到单个卡片:将
.onTapGesture添加到每个卡片视图上,确保点击事件能准确关联到被点击的卡片。 - 最短路径计算:在点击处理逻辑中,计算当前中心卡片到目标卡片的最短偏移量,避免动画绕远路。
- 动画状态更新:使用
withAnimation包裹状态更新,确保滑动过程平滑过渡,同时更新activeIndex以保持状态一致。
内容的提问来源于stack exchange,提问作者Anupam Mishra
相关产品推荐
相关产品推荐

