SwiftUI数字计数动画异常:卡顿不流畅问题排查
SwiftUI数字计数动画卡顿修复方案
问题分析
你实现的AnimatableNumber组件出现卡顿(比如停在中间值直接跳转到最大值),核心原因有三点:
- 使用
CGFloat作为动画插值载体,精度不足导致数值跳变; easeInOut动画曲线在首尾阶段插值速度过慢,使得整数取整后长时间停留在同一数值,最终突然跳转;- 未处理视图重新出现的情况,动画状态未重置。
修改后的完整代码
public struct AnimatableNumber: View { @State private var animatedValue: Double = 0 let maxNumber: Double let font: SwiftUI.Font let animationDuration: Double public init(maxNumber: Double, font: SwiftUI.Font, animationDuration: Double = 2) { self.maxNumber = maxNumber self.font = font self.animationDuration = animationDuration } public var body: some View { // 用目标数值的文本固定视图尺寸,避免动画过程中布局变化 GeometryReader { proxy in Text("\(Int(maxNumber))") .font(font) .foregroundColor(.clear) .modifier(AnimatingNumberOverlay(number: animatedValue, font: font)) .minimumScaleFactor(0.5) .frame(width: proxy.size.width, height: proxy.size.height) } .fixedSize() .onAppear { // 重置后启动动画,确保视图重新出现时也能正常播放 animatedValue = 0 withAnimation(Animation.linear(duration: animationDuration)) { animatedValue = maxNumber } } .onDisappear { animatedValue = 0 } } } private struct AnimatingNumberOverlay: AnimatableModifier { var number: Double = 0 let font: SwiftUI.Font var animatableData: Double { get { number } set { number = newValue } } func body(content: Content) -> some View { content.overlay( Text(String(format: "%.0f", number)) .font(font) ) } }
关键修改点说明
- 改用Double类型:相比CGFloat拥有更高的精度,避免插值过程中出现数值丢失或跳变;
- 线性动画曲线:使用
Animation.linear替代easeInOut,保证数值匀速变化,消除首尾阶段的停顿问题; - 状态重置逻辑:在
onDisappear中重置动画数值,确保视图重新出现时能从头开始播放动画; - 格式化数值显示:用
String(format: "%.0f", number)替代直接Int(number)转换,取整逻辑更稳定; - 固定视图尺寸:通过
GeometryReader和frame强制锁定视图大小,避免动画过程中布局变化干扰流畅度。
额外优化建议
如果目标数值极大(如10000+),固定时长的动画仍会出现较大的数值跳变,可根据数值动态调整动画时长:
// 在init中添加时长调整逻辑 let adjustedDuration: Double public init(maxNumber: Double, font: SwiftUI.Font, baseDuration: Double = 2) { self.maxNumber = maxNumber self.font = font // 每100个数值占用基础时长,可根据需求调整比例 self.adjustedDuration = baseDuration * (maxNumber / 100) }
内容的提问来源于stack exchange,提问作者DevB1
相关产品推荐
相关产品推荐

