SwiftUI录制按钮循环动画与状态异常及UIKit转SwiftUI实现
SwiftUI 录制按钮循环扩散动画修复方案
问题概述
现有SwiftUI录制按钮定义了RecordState三种状态:.none(初始)、.recording(录制中)、.notRecording(未录制),当前动画存在以下异常:
- 从
.none切换到.recording时动画正常 - 切换回
.notRecording无过渡动画 - 再次切换到
.recording时,动画出现变形叠加
错误代码示例(常见问题版本)
enum RecordState { case none, recording, notRecording } struct RecordButton: View { @Binding var state: RecordState @State private var scale: CGFloat = 1.0 var body: some View { ZStack { Circle() .stroke(Color.red) .scaleEffect(scale) .opacity(1 - (scale - 1) / 2) Circle() .fill(Color.red) .frame(width: 30, height: 30) } .frame(width: 60, height: 60) .onTapGesture { switch state { case .none, .notRecording: state = .recording withAnimation(Animation.easeInOut(duration: 1.5).repeatForever(autoreverses: false)) { scale = 3.0 } case .recording: state = .notRecording scale = 1.0 } } } } // 测试视图 struct RecordButtonTestView: View { @State private var state: RecordState = .none var body: some View { RecordButton(state: $state) } }
问题根源
- 动画与业务状态耦合:直接修改
scale属性绑定重复动画,再次触发时旧动画未被正确终止,导致多动画叠加 - 无终止过渡:切换到
.notRecording时直接重置scale,没有平滑过渡动画 - 状态同步缺失:
.none状态未同步停止动画,可能遗留动画状态
修复后的SwiftUI代码
enum RecordState { case none, recording, notRecording } struct RecordButton: View { @Binding var state: RecordState @State private var isAnimating = false var body: some View { ZStack { // 扩散圆环动画层 Circle() .stroke(Color.red, lineWidth: 2) .scaleEffect(isAnimating ? 3.0 : 1.0) .opacity(isAnimating ? 0 : 1) .animation( isAnimating ? Animation.easeInOut(duration: 1.5).repeatForever(autoreverses: false) : .easeOut(duration: 0.3), value: isAnimating ) // 中心按钮(区分录制/未录制状态) Circle() .fill(state == .recording ? Color.red : Color.gray) .frame(width: 30, height: 30) .overlay( state == .notRecording ? Circle().stroke(Color.white, lineWidth: 2) : nil ) } .frame(width: 60, height: 60) .onTapGesture { switch state { case .none, .notRecording: state = .recording isAnimating = true case .recording: state = .notRecording isAnimating = false } } // 同步none状态与动画状态 .onChange(of: state) { newState in if newState == .none { isAnimating = false } } } } // 测试视图(含重置按钮) struct RecordButtonTestView: View { @State private var state: RecordState = .none var body: some View { VStack(spacing: 20) { RecordButton(state: $state) Button("重置为初始状态") { state = .none } } .padding() } }
关键修复点
- 分离动画与业务状态:用独立的
isAnimating变量控制动画启停,避免业务状态与动画逻辑耦合 - 精准动画触发:通过
value: isAnimating让SwiftUI感知动画状态变化,确保每次切换都能正确启动/终止动画,避免叠加 - 添加终止过渡:切换到非录制状态时,扩散圆环通过
.easeOut(duration: 0.3)平滑缩回到初始状态 - 状态同步:监听
state变化,当回到.none时自动停止动画,保证状态一致性
与UIKit实现逻辑对齐
UIKit中通常通过添加/移除CAAnimationGroup实现循环扩散,修复后的SwiftUI方案完全对齐该逻辑:
- 每次启动动画前自动终止旧动画(SwiftUI通过状态切换管理动画生命周期)
- 停止时添加平滑过渡动画,而非直接重置属性
- 分离业务状态与动画控制状态,避免耦合异常
内容的提问来源于stack exchange,提问作者imjohnnotsina
相关产品推荐
相关产品推荐

