SwiftUI 如何实现可重复触发的一次性动画,无需asyncAfter手动复位State
无asyncAfter的得分缩放动画自动重置实现方案
核心思路是绑定动画的生命周期回调,完全依靠系统动画的结束信号触发State重置,不会出现手动计时和动画时长不匹配的问题。
方案1:withAnimation完成回调(适用于iOS 17+,最简洁)
iOS 17之后系统原生为withAnimation新增了动画完成回调,直接在回调内重置触发变量即可:
// 分别维护两名玩家的动画触发State @State private var animatePlayer1Score = false @State private var animatePlayer2Score = false @State private var player1Score = 0 @State private var player2Score = 0 // 玩家1得分更新逻辑示例 func addScoreForPlayer1() { player1Score += 1 withAnimation(.easeInOut(duration: 0.3)) { animatePlayer1Score = true } completion: { // 动画执行完成后自动重置,无需手动计时 animatePlayer1Score = false } } // 组件绑定 var body: some View { HStack(spacing: 50) { Text("\(player1Score)") .scaleEffect(animatePlayer1Score ? 1.5 : 1.0) Text("\(player2Score)") .scaleEffect(animatePlayer2Score ? 1.5 : 1.0) } }
方案2:自定义AnimatableModifier(适用于iOS 13+,全版本兼容)
如果需要适配更低系统版本,可以通过自定义遵循Animatable协议的修饰符,监听动画进度,在进度到100%时自动触发重置:
// 自定义动画修饰符 struct ScoreScaleEffect: AnimatableModifier { var progress: Double let scaleRange: Double = 0.5 let onFinish: () -> Void var animatableData: Double { get { progress } set { progress = newValue // 动画完成时触发回调 if newValue == 1 { DispatchQueue.main.async { onFinish() } } } } func body(content: Content) -> some View { content.scaleEffect(1 + progress * scaleRange) } } extension View { func scoreScaleAnimation(progress: Double, onFinish: @escaping () -> Void) -> some View { self.modifier(ScoreScaleEffect(progress: progress, onFinish: onFinish)) } }
使用方式:
@State private var player1AnimProgress = 0.0 @State private var player1Score = 0 func addScoreForPlayer1() { player1Score += 1 withAnimation(.easeInOut(duration: 0.3)) { player1AnimProgress = 1.0 } } // 组件绑定 Text("\(player1Score)") .scoreScaleAnimation(progress: player1AnimProgress) { // 动画完成自动重置 player1AnimProgress = 0.0 }
两名玩家的得分动画只需各自维护独立的触发变量即可实现完全独立的触发逻辑,互不影响。
内容的提问来源于stack exchange,提问作者Nimrod Shai
相关产品推荐
相关产品推荐

