You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI卡片轮播:实现点击卡片滑动至屏幕中央

实现点击卡片滑动至中央的效果

要实现点击任意卡片使其平滑滑动到屏幕中央的效果,你需要对现有代码做两处关键修改:

  1. 将点击手势从整体的ZStack移至每个卡片视图上,这样才能识别具体被点击的卡片。
  2. 在点击手势的处理逻辑中,计算当前中心卡片到目标卡片的最短路径,并通过动画更新滑动状态。

以下是修改后的完整代码:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 06:25:53