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

UIViewPropertyAnimator切换动画中断:是否为预期行为及解决办法

UIViewPropertyAnimator 动画中断无过渡问题解答

这是预期行为吗?

是预期行为。UIViewPropertyAnimator 和 UIView.animate 的底层处理逻辑不一样:

  • UIView.animate 属于隐式动画,系统会自动处理动画的中断与过渡——当新动画触发时,会基于视图当前的实时状态(比如动画执行到一半的 alpha 值)继续平滑执行新动画。
  • 而你现在的写法里,每次调用 refreshView 都会创建一个全新的 UIViewPropertyAnimator 实例,这是显式动画对象。新动画启动时,旧的 animator 会被立即取消(进入 .cancelled 或 .current 状态),视图会直接跳到目标值,自然没有过渡效果。

解决方法

核心思路是复用同一个 animator 实例,而不是每次创建新的。以下是两种可行的实现方式:

方式一:复用 animator 并基于剩余时长调整动画

var isShowingView = false
var animator: UIViewPropertyAnimator? // 把 animator 改成成员变量

func onButtonClick() {
    isShowingView.toggle()
    refreshView()
}

func refreshView() {
    let targetAlpha: CGFloat = isShowingView ? 1 : 0
    let timing = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.4, y: 0.0),
                                         controlPoint2: CGPoint(x: 0.2, y: 1.0))

    // 无正在执行的动画,直接创建新动画
    guard let existingAnimator = animator, existingAnimator.isRunning else {
        animator = UIViewPropertyAnimator(duration: 0.3, timingParameters: timing)
        animator?.addAnimations {
            self.myView.alpha = targetAlpha
        }
        animator?.addCompletion { [weak self] _ in
            self?.animator = nil // 动画结束后清空,避免无用持有
        }
        animator?.startAnimation()
        return
    }

    // 有正在执行的动画,基于当前状态平滑过渡到新目标
    existingAnimator.pauseAnimation()
    let remainingDuration = existingAnimator.duration * existingAnimator.fractionComplete
    // 从当前 alpha 值动画到新目标
    animator = UIViewPropertyAnimator(duration: remainingDuration, timingParameters: timing)
    animator?.addAnimations {
        self.myView.alpha = targetAlpha
    }
    animator?.addCompletion { [weak self] _ in
        self?.animator = nil
    }
    animator?.startAnimation()
}

方式二:用 continueAnimation 简化过渡逻辑

这种方式直接调整正在执行的动画,代码更简洁:

var isShowingView = false
var animator: UIViewPropertyAnimator?

func onButtonClick() {
    isShowingView.toggle()
    refreshView()
}

func refreshView() {
    let targetAlpha: CGFloat = isShowingView ? 1 : 0
    let timing = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.4, y: 0.0),
                                         controlPoint2: CGPoint(x: 0.2, y: 1.0))

    if let existingAnimator = animator, existingAnimator.isRunning {
        // 停止当前动画但保留视图的当前状态
        existingAnimator.stopAnimation(false)
        existingAnimator.finishAnimation(at: .current)
        // 基于当前状态启动新动画到目标值
        animator = UIViewPropertyAnimator(duration: 0.3, timingParameters: timing)
        animator?.addAnimations {
            self.myView.alpha = targetAlpha
        }
        animator?.startAnimation()
    } else {
        // 无正在执行的动画,直接创建并启动
        animator = UIViewPropertyAnimator(duration: 0.3, timingParameters: timing)
        animator?.addAnimations {
            self.myView.alpha = targetAlpha
        }
        animator?.addCompletion { [weak self] _ in
            self?.animator = nil
        }
        animator?.startAnimation()
    }
}

关键总结

  • UIView.animate 之所以能平滑过渡,是因为系统在底层帮你封装了 animator 的复用与状态衔接逻辑。
  • 直接每次创建新的 UIViewPropertyAnimator 会导致旧动画被强制中断,视图直接跳变。
  • 复用 animator 并基于视图当前状态调整动画目标,才能实现和 UIView.animate 一致的平滑过渡效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 06:16:08