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

自定义SegmentedControl流体动画实现方案求助

实现带拉伸效果的SegmentedControl指示器动画

你的现有方案是给每个选项单独设置圆形背景,切换时只是各自的显隐动画,没法实现连贯的拉伸移动效果。要做这种动画,得把选中的白色指示器做成独立的可移动视图,通过位置偏移和缩放组合来实现先拉伸后恢复的效果。

下面是具体实现代码,我会逐部分解释:

import SwiftUI

// 用于收集每个选项的位置信息
private struct SegmentPreferenceKey: PreferenceKey {
    static var defaultValue: [RotationCase: CGRect] = [:]
    
    static func reduce(value: inout [RotationCase: CGRect], nextValue: () -> [RotationCase: CGRect]) {
        value.merge(nextValue()) { $1 }
    }
}

struct SegmentedControl: View {
    @State var position: RotationCase = .P1
    // 存储每个选项的位置信息
    @State private var segmentFrames: [RotationCase: CGRect] = [:]
    
    var body: some View {
        ZStack(alignment: .leading) {
            // 白色指示器:单独的视图,负责移动和拉伸动画
            RoundedRectangle(cornerRadius: 22.5) // 和容器Capsule圆角匹配
                .foregroundColor(.white)
                .frame(width: segmentFrames[position]?.width ?? 0, height: 45)
                .offset(x: segmentFrames[position]?.minX ?? 0)
                .animation(
                    Animation.easeInOut(duration: 0.3)
                        .delay(0.05),
                    value: position
                )
                .scaleEffect(x: isAnimating ? 1.2 : 1.0, y: 1.0)
                .animation(
                    Animation.easeInOut(duration: 0.2)
                        .repeatCount(2, autoreverses: true),
                    value: isAnimating
                )
            
            HStack(spacing: 0) {
                ForEach(RotationCase.allCases, id: \.self) { rcase in
                    Text(rcase.rawValue)
                        .foregroundColor(rcase == position ? .black : .white)
                        .padding()
                        .background(Color.clear) // 去掉原来的背景,让指示器在底层显示
                        .onTapGesture {
                            isAnimating = true
                            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                                withAnimation {
                                    position = rcase
                                }
                                DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
                                    isAnimating = false
                                }
                            }
                        }
                        // 收集每个选项的位置信息
                        .background(
                            GeometryReader { geo in
                                Color.clear
                                    .preference(
                                        key: SegmentPreferenceKey.self,
                                        value: [rcase: geo.frame(in: .named("SegmentContainer"))]
                                    )
                            }
                        )
                }
            }
        }
        .frame(height: 45)
        .padding(10)
        .background(.black)
        .clipShape(Capsule())
        .shadow(radius: 8)
        .coordinateSpace(name: "SegmentContainer")
        .onPreferenceChange(SegmentPreferenceKey.self) {
            segmentFrames = $0
        }
    }
    
    // 用于标记是否处于动画状态
    @State private var isAnimating = false
}

enum RotationCase: String, CaseIterable {
    case P1 = "P1", P2 = "P2", P3 = "P3", P4 = "P4", P5 = "P5", P6 = "P6"
}

// 预览
struct SegmentedControl_Previews: PreviewProvider {
    static var previews: some View {
        SegmentedControl()
    }
}

关键实现点说明:

  • 独立指示器:把白色背景做成单独的RoundedRectangle,放在ZStack底层,文字层在上方,这样就能实现指示器的移动效果。
  • 位置收集:用GeometryReader和自定义PreferenceKey收集每个选项在容器内的位置和尺寸,确保指示器能精准移动到目标位置。
  • 拉伸+移动组合动画:
    1. 点击时先触发横向拉伸动画(scaleEffect),重复一次自动反转。
    2. 延迟一小段时间后,启动指示器的位置偏移动画。
    3. 动画结束后关闭拉伸状态,恢复圆形(这里用圆角矩形,和容器Capsule匹配,视觉上是圆形)。
  • 动画时序控制:用DispatchQueue延迟执行,让拉伸和移动动画衔接更自然,避免冲突。

优化建议:

  • 可以调整动画的duration和delay参数,找到最适合的流畅度。
  • 如果需要更细腻的拉伸效果,可以根据移动距离动态调整拉伸比例(比如移动越远,拉伸幅度越大)。

内容的提问来源于stack exchange,提问作者carbonedavidee

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 17:22:51