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.AnimatorListener 和 Animator.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 知识体系总览
我把这一章的核心逻辑画了张图,方便你理解监听器之间的关系:
这张图把监听体系分成了两条主线:左边是生命周期监听,右边是帧更新监听。适配器作为辅助工具,帮你简化左边的代码量。
6.6 避坑指南
最后分享几个我踩过的坑:
- 不要在 onAnimationUpdate 里做耗时操作——它每帧都调用,60fps 就是每秒 60 次。我之前在里面做了个数据库查询,直接卡成 PPT。
- 取消动画后记得恢复状态——用户按返回键或者切到后台,动画可能被系统取消。如果你在 onAnimationStart 里隐藏了某个 View,记得在 onAnimationCancel 里把它显示回来。
- 监听器引用泄漏——如果你在匿名内部类里持有了 Activity 的引用,而动画是无限重复的,Activity 销毁时可能无法被 GC 回收。建议用弱引用或者在 onDestroy 里移除监听器。
- AnimatorListener 管「什么时候」,AnimatorUpdateListener 管「怎么样了」
- AnimatorListenerAdapter 帮你省掉空方法,代码更清爽
- onAnimationCancel 和 onAnimationEnd 互斥,两边都要处理资源释放
- 帧回调里别做重活,60fps 的压力比你想象的大