6、属性动画监听与回调:AnimatorListener与AnimatorUpdateListener、适配器(AnimatorListenerAdapter)简化代码

属性动画跑起来了,但很多时候我们需要知道它什么时候开始、什么时候结束、或者每一帧的值变化。这时候就需要监听器登场了。

说白了,监听器就是动画的「哨兵」。你派它站在那,动画每动一下、每到一个关键节点,它就给你汇报一声。我个人习惯在需要做链式动画或者状态同步时,一定会用到它们。

6.1 AnimatorListener:动画生命周期全监听

AnimatorListener 是 Android 属性动画最核心的监听接口。它定义了四个回调方法,对应动画的四个生命周期阶段:

方法 触发时机 典型用途
onAnimationStart() 动画开始播放时 初始化状态、显示加载指示器
onAnimationEnd() 动画正常结束时 回收资源、触发下一步操作
onAnimationCancel() 动画被取消时 恢复初始状态、清理临时数据
onAnimationRepeat() 动画重复播放时 更新计数、改变方向

来看一个完整的例子:

val animator = ObjectAnimator.ofFloat(view, "translationX", 0f, 300f)
animator.duration = 1000
animator.addListener(object : Animator.AnimatorListener {
    override fun onAnimationStart(animation: Animator) {
        Log.d("TAG", "动画开始了")
        // 我习惯在这里把按钮置为不可点击,防止用户重复触发
        button.isEnabled = false
    }

    override fun onAnimationEnd(animation: Animator) {
        Log.d("TAG", "动画结束了")
        button.isEnabled = true
    }

    override fun onAnimationCancel(animation: Animator) {
        Log.d("TAG", "动画被取消了")
        // 我曾经在这里忘记恢复状态,导致按钮一直灰掉
        button.isEnabled = true
    }

    override fun onAnimationRepeat(animation: Animator) {
        Log.d("TAG", "动画重复了")
    }
})
animator.start()

嗯,这里有个坑要注意:onAnimationCancel()onAnimationEnd() 是互斥的。动画被取消时,onAnimationEnd() 不会再被调用。所以如果你在 onAnimationEnd() 里做了资源释放,别忘了在 onAnimationCancel() 里也做一遍。

⚠️ 注意: 如果你调用了 animator.cancel(),系统只会触发 onAnimationCancel(),不会触发 onAnimationEnd()。反之,animator.end() 会直接跳到最终值并触发 onAnimationEnd(),但不会触发 onAnimationCancel()

6.2 AnimatorUpdateListener:每一帧都不放过

如果说 AnimatorListener 是看大门的,那 AnimatorUpdateListener 就是盯监控的。它只有一个方法 onAnimationUpdate(),在动画的每一帧都会被调用。

你想想看,这个监听器能干什么?

  • 实时获取当前动画的进度值
  • 手动更新多个属性(比如同时改变位置和透明度)
  • 做自定义的插值计算
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = 2000
animator.addUpdateListener { animation ->
    val fraction = animation.animatedFraction  // 0.0 ~ 1.0
    val value = animation.animatedValue as Float

    // 同时更新多个属性
    view.translationX = value * 300f
    view.alpha = 1f - value * 0.5f

    // 我曾在项目里用这个做进度条动画,效果很丝滑
    progressBar.progress = (fraction * 100).toInt()
}
animator.start()

这里有个小技巧:animatedFraction 返回的是 0 到 1 的进度百分比,不受插值器影响。而 animatedValue 是经过插值器处理后的实际值。如果你需要做「线性进度条 + 非线性动画」的组合,用 animatedFraction 更靠谱。

6.3 AnimatorListenerAdapter:少写一堆空方法

每次实现 AnimatorListener 都要写四个方法,哪怕你只关心 onAnimationEnd。代码冗余不说,看着也烦。

Android 提供了 AnimatorListenerAdapter,它是一个空实现类。你只需要重写你需要的方法即可:

animator.addListener(object : AnimatorListenerAdapter() {
    override fun onAnimationEnd(animation: Animator) {
        // 我只关心结束回调
        view.visibility = View.GONE
    }
    // onAnimationStart、onAnimationCancel、onAnimationRepeat 都不用写
})

我个人非常推荐这种做法。它让代码更干净,意图更明确。你想想看,别人读你代码的时候,一眼就知道「哦,这个人只关心动画结束后的处理」,而不是在一堆空方法里找逻辑。

💡 提示: AnimatorListenerAdapter 同时实现了 Animator.AnimatorListenerAnimator.AnimatorPauseListener 两个接口。如果你需要监听动画暂停/恢复事件(onAnimationPause / onAnimationResume),也可以直接重写对应方法。

6.4 实战:监听器组合使用

在实际项目中,我经常把两个监听器搭配使用。比如做一个「点击卡片弹出详情」的动画:

val slideUp = ObjectAnimator.ofFloat(detailView, "translationY", detailView.height.toFloat(), 0f)
slideUp.duration = 300
slideUp.interpolator = OvershootInterpolator()

slideUp.addListener(object : AnimatorListenerAdapter() {
    override fun onAnimationStart(animation: Animator) {
        detailView.visibility = View.VISIBLE
        // 防止动画过程中用户再次点击
        cardView.isClickable = false
    }

    override fun onAnimationEnd(animation: Animator) {
        cardView.isClickable = true
    }

    override fun onAnimationCancel(animation: Animator) {
        // 我曾经忘记恢复点击状态,用户以为卡死了
        cardView.isClickable = true
        detailView.visibility = View.GONE
    }
})

slideUp.addUpdateListener { animation ->
    // 根据动画进度同步调整背景透明度
    val fraction = animation.animatedFraction
    overlayView.alpha = fraction * 0.6f
}

slideUp.start()

你看,AnimatorListenerAdapter 负责生命周期管理,AnimatorUpdateListener 负责实时同步。各司其职,代码结构很清晰。

6.5 知识体系总览

我把这一章的核心逻辑画了张图,方便你理解监听器之间的关系:

属性动画监听体系 属性动画 AnimatorListener onAnimationStart onAnimationEnd onAnimationCancel onAnimationRepeat AnimatorUpdateListener onAnimationUpdate(每帧调用) AnimatorListenerAdapter 适配器模式:AnimatorListenerAdapter 提供空实现,只需重写需要的方法

这张图把监听体系分成了两条主线:左边是生命周期监听,右边是帧更新监听。适配器作为辅助工具,帮你简化左边的代码量。

6.6 避坑指南

最后分享几个我踩过的坑:

  • 不要在 onAnimationUpdate 里做耗时操作——它每帧都调用,60fps 就是每秒 60 次。我之前在里面做了个数据库查询,直接卡成 PPT。
  • 取消动画后记得恢复状态——用户按返回键或者切到后台,动画可能被系统取消。如果你在 onAnimationStart 里隐藏了某个 View,记得在 onAnimationCancel 里把它显示回来。
  • 监听器引用泄漏——如果你在匿名内部类里持有了 Activity 的引用,而动画是无限重复的,Activity 销毁时可能无法被 GC 回收。建议用弱引用或者在 onDestroy 里移除监听器。
📌 核心要点:
  • AnimatorListener 管「什么时候」,AnimatorUpdateListener 管「怎么样了」
  • AnimatorListenerAdapter 帮你省掉空方法,代码更清爽
  • onAnimationCancel 和 onAnimationEnd 互斥,两边都要处理资源释放
  • 帧回调里别做重活,60fps 的压力比你想象的大

公众号:蓝海资料掘金营,微信deep3321