「SwiftUI」实现卡片左右滑动切换效果
最近有个需求,需要使用SwiftUI实现一个卡片滑动效果,左右滑动切换下一张卡片。可以缓慢拖动,拖的时候卡片会跟着手指动
还会像扇子旋转角度。
首先构思一个页面的基本结构
主界面
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Text("单向滑动卡片")
.font(.title)
.padding()
CardStackView()
.frame(height: 500)
}
}
}
// 预览代码
#Preview {
ContentView()
}
创建卡片样式
struct CardView: View {
let text: String
let color: Color
var body: some View {
RoundedRectangle(cornerRadius: 20)
.fill(color)
.frame(width: 300, height: 400)
.overlay(
Text(text)
.font(.largeTitle)
.foregroundColor(.white)
)
.shadow(radius: 10)
}
}
实现卡堆的主要逻辑
struct CardStackView: View {
// 卡片数据
let cards = [
(text: "卡片1", color: Color.blue),
(text: "卡片2", color: Color.green),
(text: "卡片3", color: Color.orange),
(text: "卡片4", color: Color.purple),
(text: "卡片5", color: Color.red)
]
// 当前显示的卡片索引
@State private var currentIndex = 0
// 滑动相关状态
@State private var offset: CGFloat = 0
@State private var rotation: Double = 0
@State private var isSwiping = false
var body: some View {
ZStack {
// 只显示当前卡片和下一张卡片
if currentIndex + 1 < cards.count {
CardView(text: cards[currentIndex + 1].text,
color: cards[currentIndex + 1].color)
.zIndex(1)
}
if currentIndex < cards.count {
CardView(text: cards[currentIndex].text,
color: cards[currentIndex].color)
.zIndex(2)
.offset(x: offset, y: isSwiping ? -abs(offset/10) : 0)
.rotationEffect(.degrees(rotation), anchor: .bottom)
.animation(.interactiveSpring(), value: offset)
.gesture(
DragGesture()
.onChanged { value in
isSwiping = true
offset = value.translation.width
// 限制旋转角度在-30到30度之间
rotation = min(max(Double(value.translation.width / 10), -30), 30)
}
.onEnded { value in
// 滑动距离超过阈值则切换卡片
if abs(value.translation.width) > 100 {
withAnimation(.easeOut(duration: 0.3)) {
// 根据滑动方向决定飞出方向
let direction = value.translation.width > 0 ? 1 : -1
offset = direction * UIScreen.main.bounds.width
rotation = direction * 45
}
// 延迟切换到下一张卡片
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
goToNextCard()
}
} else {
// 复位
withAnimation(.spring()) {
resetCardPosition()
}
}
}
)
} else {
// 所有卡片已显示完毕
Text("没有更多卡片了")
.font(.title)
}
}
}
private func goToNextCard() {
currentIndex += 1
resetCardPosition()
}
private func resetCardPosition() {
offset = 0
rotation = 0
isSwiping = false
}
}
以上就是完整实现的过程和思路
其实效果实现起来也不是很复杂,实现关键主要是以下几点:
旋转锚点设置
.rotationEffect(.degrees(rotation), anchor: .bottom)
将旋转锚点设置在底部,实现更自然的扇形效果
动画效果
- 拖动时使用
.interactiveSpring()
实现跟随手感 - 飞出时使用
.easeOut(duration: 0.3)
实现流畅退出 - 复位时使用
.spring()
实现弹性效果
厉害哦