4、属性动画(Property Animation)入门:ValueAnimator核心机制、ObjectAnimator使用、ofInt/ofFloat/ofObject
说实话,刚接触Android动画那会儿,我总觉得View动画已经够用了。不就是平移、缩放、旋转嘛,一个AnimationSet全搞定。直到有一次,我需要让一个按钮从红色渐变到蓝色,同时它的点击区域也要跟着移动——View动画只能改变绘制位置,不能改变真实坐标。嗯,这就是我转向属性动画的契机。
属性动画,说白了就是直接修改对象的属性值。你给它一个起始值、一个结束值,再告诉它跑多久,它就在这段时间里不断计算中间值,然后设置到目标对象上。核心就是三个字:值变化。
ValueAnimator:一切动画的发动机
我个人习惯把ValueAnimator叫做「数值发动机」。它不直接操作任何UI控件,只负责一件事:在指定时间内,从一个数值变化到另一个数值,然后通过监听器告诉你「当前值是多少」。
来看个最简单的例子:
val animator = ValueAnimator.ofFloat(0f, 100f)
animator.duration = 1000
animator.addUpdateListener { animation ->
val value = animation.animatedValue as Float
// 拿到当前值,你想怎么用就怎么用
textView.translationX = value
}
animator.start()
这里ofFloat创建了一个从0到100的浮点数动画。动画跑起来后,每一帧都会回调onAnimationUpdate,我拿到当前值,手动设置给textView的translationX。你看,ValueAnimator不关心你拿这个值去干嘛,它只管生产数值。
核心机制拆解:
- 时间插值器(TimeInterpolator):决定动画的节奏,比如匀速、加速、弹跳。默认是加速减速插值器。
- 估值器(TypeEvaluator):根据进度计算当前值。比如
FloatEvaluator做线性插值,ArgbEvaluator做颜色插值。 - 帧刷新机制:依赖Choreographer,每16ms(60fps)触发一次计算。
我曾经踩过一个坑:在onAnimationUpdate里做耗时操作,结果动画卡成PPT。记住,这个回调是在UI线程执行的,别在里面做数据库查询或者网络请求。
ObjectAnimator:更省事的写法
如果你觉得每次都要写addUpdateListener太麻烦,那ObjectAnimator就是为你准备的。它继承自ValueAnimator,但多了一个能力:直接操作对象的属性。
val animator = ObjectAnimator.ofFloat(
textView, // 目标对象
"translationX", // 属性名
0f, // 起始值
200f // 结束值
)
animator.duration = 1000
animator.start()
你看,代码少了一半。你只需要告诉它「操作谁、改哪个属性、从哪到哪」,剩下的它自己搞定。原理其实很简单:通过反射调用属性的set方法。所以目标对象必须有对应的setTranslationX(float)方法。
注意:如果属性没有set方法,ObjectAnimator会直接崩溃。比如你想动画化TextView的text属性,但TextView没有setText(float)方法——嗯,我当年就因为这个查了半天bug。
ofInt、ofFloat、ofObject:三种估值方式
这三种方法对应不同的数据类型,背后用的是不同的估值器。
| 方法 | 数据类型 | 默认估值器 | 适用场景 |
|---|---|---|---|
ofInt |
Int |
IntEvaluator |
整数变化,比如透明度0→255 |
ofFloat |
Float |
FloatEvaluator |
小数变化,比如平移、缩放 |
ofObject |
任意类型 | 需要自定义TypeEvaluator |
颜色、坐标点、自定义对象 |
举个例子,颜色动画就得用ofObject:
val colorAnim = ObjectAnimator.ofObject(
textView,
"textColor",
ArgbEvaluator(), // 颜色估值器
Color.RED,
Color.BLUE
)
colorAnim.duration = 2000
colorAnim.start()
这里ArgbEvaluator会帮我们把红色到蓝色之间的颜色值算出来。如果你要动画化自定义对象,比如一个Point,那就得自己写TypeEvaluator了:
class PointEvaluator : TypeEvaluator<Point> {
override fun evaluate(fraction: Float, startValue: Point, endValue: Point): Point {
val x = startValue.x + (endValue.x - startValue.x) * fraction
val y = startValue.y + (endValue.y - startValue.y) * fraction
return Point(x.toInt(), y.toInt())
}
}
说白了,fraction就是当前进度(0到1),你根据这个进度算出中间值就行。
知识体系总览
下面这张图帮你理清属性动画的核心脉络:
我的小建议:刚开始学属性动画,先用ValueAnimator配合addUpdateListener写一遍,理解数值变化的本质。然后再用ObjectAnimator简化代码。这样即使以后遇到复杂场景,你也能自己用ValueAnimator拼出来。
最后说一句,属性动画的灵活性远不止这些。你还可以设置repeatCount让动画循环,用setInterpolator改变运动曲线,甚至用AnimatorSet组合多个动画。这些我们后面会慢慢展开。今天先把ValueAnimator和ObjectAnimator吃透,后面的路就好走了。
公众号:蓝海资料掘金营,微信deep3321