如何为Safe Area内的内容添加拖拽弹性展开功能?
实现拖拽Capsule弹性展开Bottom Sheet效果
要实现拖拽胶囊手柄展开/收起底部区域的弹性动画效果,可以通过DragGesture结合弹簧动画来实现,以下是修改后的完整代码:
struct ContentView: View { @State private var isExpanded = false @State private var dragOffset: CGFloat = 0 // 定义收起和展开状态的高度 private let collapsedHeight: CGFloat = 80 private let expandedHeight: CGFloat = 160 var body: some View { ScrollView { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundColor(.accentColor) Text("Hello, world!") } .frame(maxWidth: .infinity) .padding() } .safeAreaInset(edge: .bottom) { VStack(spacing: 24) { Capsule() .frame(width: 50, height: 5) .opacity(0.5) // 添加拖拽手势 .gesture( DragGesture() .onChanged { value in // 限制拖拽范围,仅处理向上拖拽的有效偏移 dragOffset = max(-(expandedHeight - collapsedHeight), value.translation.height) } .onEnded { value in // 设置切换状态的拖拽阈值 let threshold = (expandedHeight - collapsedHeight) * 0.3 withAnimation(.spring(response: 0.4, dampingFraction: 0.7)) { if abs(dragOffset) > threshold { isExpanded.toggle() } dragOffset = 0 } } ) HStack(spacing: 16) { Image(systemName: "list.bullet") Spacer() VStack { Text("Track 2") } .font(.caption) Spacer() Image(systemName: "backward.fill") Image(systemName: "play.fill") Image(systemName: "forward.fill") Spacer() Button { withAnimation(.spring(response: 0.4, dampingFraction: 0.7)) { isExpanded.toggle() } } label: { Image(systemName: "ellipsis.circle") } } // 拖拽过程中也显示展开内容,提升交互流畅度 if isExpanded || dragOffset != 0 { HStack { Image(systemName: "airplayaudio") Image(systemName: "timer") Spacer() Text("Artist") } } } .padding([.bottom, .horizontal]) .padding(.top, 8) .background(.ultraThinMaterial) // 根据状态和拖拽偏移量动态调整底部区域高度 .frame(height: isExpanded ? expandedHeight : collapsedHeight + dragOffset) } } }
关键实现说明:
- 拖拽手势控制:给胶囊手柄添加
DragGesture,拖拽过程中实时更新偏移量并限制范围,松开时根据拖拽距离阈值判断是否切换展开状态。 - 弹性动画效果:使用
.spring动画实现自然的弹性拉伸/收缩效果,可通过调整response和dampingFraction参数自定义动画的快慢和弹性程度。 - 动态高度适配:底部区域高度根据展开状态和拖拽偏移量实时计算,拖拽时跟随手势拉伸,松开后平滑过渡到目标状态。
- 交互体验优化:拖拽过程中提前显示展开内容,避免状态切换时的突兀感。
内容的提问来源于stack exchange,提问作者TruMan1
相关产品推荐
相关产品推荐

