一、从补间动画到属性动画:我为什么要重新学动画
记得我刚入行那会儿,Android 动画还停留在 View 动画时代。说白了就是平移、缩放、旋转、透明度这四板斧。用起来倒是简单,但坑也不少。
有一次我做了一个按钮的位移动画,想让按钮从屏幕左边滑到右边。用 View 动画写完,效果看着没问题。结果测试一跑,发现按钮虽然「看起来」移过去了,点击事件却还在原地。嗯,这就是 View 动画最大的问题——它只改变了 View 的绘制位置,没改变 View 的真实属性。
所以 Google 在 Android 3.0 推出了属性动画。今天我们就从最核心的三个东西讲起:ValueAnimator、ObjectAnimator、PropertyValuesHolder。
核心区别一句话:View 动画是「画皮」,属性动画是「换骨」。属性动画真正改变了对象的属性值,不只是视觉欺骗。
二、ValueAnimator:属性动画的发动机
ValueAnimator 是属性动画的底层核心。它不直接操作任何 UI 控件,只做一件事——在指定时间内,从一个值变到另一个值。
你可以把它理解成一个「数值发生器」。你告诉它起始值、结束值、时长,它就会按帧给你吐出中间值。至于这些值怎么用,那是你的事。
2.1 基本用法
ValueAnimator animator = ValueAnimator.ofFloat(0f, 100f);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
// 拿到值,你想怎么用就怎么用
view.setTranslationX(value);
}
});
animator.start();
你看,ValueAnimator 只负责「算数」,不负责「干活」。它把每一帧的数值回调给你,你在回调里自己去更新 View 的属性。
2.2 插值器和估值器
这里有两个概念我当年绕了好久——插值器(Interpolator)和估值器(TypeEvaluator)。
| 角色 | 作用 | 通俗理解 |
|---|---|---|
| 插值器 | 控制动画的速率变化 | 决定「快慢节奏」——先快后慢?还是匀速? |
| 估值器 | 根据进度计算具体属性值 | 决定「怎么算」——线性变化?还是抛物线? |
举个例子。你开车从 A 到 B:
- 插值器决定你什么时候踩油门、什么时候踩刹车
- 估值器决定你的位置怎么算——是匀速直线运动,还是先加速后减速
系统默认的插值器是 AccelerateDecelerateInterpolator,也就是先加速后减速。我个人习惯,如果是做列表入场动画,用 DecelerateInterpolator 效果更自然。
2.3 自定义估值器
ValueAnimator 默认支持 int、float、颜色值。但如果你想动画一个自定义对象,比如 Point,就需要自己写估值器。
public class PointEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
float x = startValue.x + fraction * (endValue.x - startValue.x);
float y = startValue.y + fraction * (endValue.y - startValue.y);
return new Point((int) x, (int) y);
}
}
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
我在项目中遇到过需要做「贝塞尔曲线路径动画」的场景,就是靠自定义估值器实现的。说白了,估值器给了你完全的自由度。
小技巧:如果你只是做简单的位移、缩放,用 ObjectAnimator 就够了,不用自己写 ValueAnimator。ValueAnimator 更适合那些「没有现成 setter 方法」的属性。
三、ObjectAnimator:最常用的属性动画
ObjectAnimator 是 ValueAnimator 的子类。它帮我们省去了手动写 addUpdateListener 的麻烦。
你只需要告诉它三件事:
- 操作哪个对象
- 操作哪个属性
- 起始值和结束值
剩下的,它自动通过反射调用属性的 setter 方法。
3.1 基本用法
ObjectAnimator animator = ObjectAnimator.ofFloat(
view, // 目标对象
"translationX", // 属性名
0f, // 起始值
200f // 结束值
);
animator.setDuration(1000);
animator.start();
这行代码的效果,等价于我们刚才用 ValueAnimator 写的那一大段。ObjectAnimator 内部自动调用了 view.setTranslationX()。
3.2 属性名必须要有 setter 方法
这里有个大坑——属性名必须对应一个 public 的 setter 方法。
比如 "translationX" 对应 setTranslationX(float)。如果你写了个不存在的属性名,或者 setter 方法不是 public 的,动画会直接崩溃。
我曾经踩过这个坑:想动画一个自定义 View 里的私有变量 mProgress,直接写了 "mProgress" 作为属性名,结果运行时抛异常。后来老老实实加了 public 的 setter 方法才搞定。
注意:ObjectAnimator 要求目标对象必须有对应属性的 getter 和 setter 方法。getter 用于获取起始值(如果你没传起始值),setter 用于更新属性值。
3.3 常用属性一览
对于 View 对象,以下属性可以直接用:
| 属性名 | 对应方法 | 说明 |
|---|---|---|
| alpha | setAlpha(float) | 透明度,0~1 |
| translationX | setTranslationX(float) | X 轴偏移 |
| translationY | setTranslationY(float) | Y 轴偏移 |
| rotation | setRotation(float) | 旋转角度 |
| scaleX | setScaleX(float) | X 轴缩放 |
| scaleY | setScaleY(float) | Y 轴缩放 |
四、PropertyValuesHolder:组合多个动画
有时候你想同时改变一个 View 的多个属性。比如让一个按钮一边移动一边旋转。
最直接的做法是开两个 ObjectAnimator,然后同时 start。但这样管理起来麻烦,而且不好控制同步。
PropertyValuesHolder 就是来解决这个问题的。
4.1 基本用法
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("translationX", 0f, 200f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("translationY", 0f, 100f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofFloat("rotation", 0f, 360f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY, pvhRotation);
animator.setDuration(2000);
animator.start();
这样写的好处是:多个属性在同一个动画实例里执行,共享同一个插值器和时长。同步性有保障,代码也更干净。
4.2 为什么不用 AnimatorSet?
你可能会问:那 AnimatorSet 呢?
区别在于:
- PropertyValuesHolder:多个属性同时变化,共享一个时间轴
- AnimatorSet:多个动画可以设置先后顺序、延迟、并行等复杂编排
简单场景用 PropertyValuesHolder 就够了。复杂编排才上 AnimatorSet。
我的习惯:如果只是两三个属性同时动,用 PropertyValuesHolder。如果动画有先后顺序,或者需要循环、反转等复杂控制,用 AnimatorSet。
五、知识体系总览
下面这张图帮你理清今天讲的内容:
六、总结
今天的内容就这些。来快速回顾一下:
- ValueAnimator 是属性动画的底层引擎,负责数值生成。你需要自己写监听器去更新属性。
- ObjectAnimator 是 ValueAnimator 的子类,通过反射自动调用 setter 方法,用起来更简单。
- PropertyValuesHolder 让你在一个动画里同时操作多个属性,适合「多属性并行变化」的场景。
这三个东西,是属性动画的基石。你想想看,从 View 动画到属性动画,最大的进步是什么?是「真实」。属性动画不再骗人,它实实在在地改变了对象的属性值。这也是为什么后来 Compose 动画能做得那么自然——因为底层的理念,就是从属性动画延续下来的。
嗯,今天就先聊到这儿。下一节我们会深入 AnimatorSet 和动画监听器,到时候再细聊。
公众号:蓝海资料掘金营,微信deep3321