4、View动画之插值器与估值器:系统插值器详解、自定义插值器、TypeEvaluator的使用
说到View动画,有两个核心概念你绕不开——插值器和估值器。很多新手容易把它们搞混,其实说白了:插值器决定动画变化的速率,估值器决定动画变化的具体数值。我刚开始学的时候也迷糊过一阵,后来在项目里踩了几个坑,才算真正搞明白。
插值器是什么?
插值器(Interpolator),它的任务很简单:根据时间流逝的百分比,计算出当前属性值改变的百分比。
举个例子。假设动画从0到1,持续1000ms。如果不用插值器,默认是线性匀速——每过100ms,值增加0.1。但如果你用了加速插值器,前200ms可能只走到0.1,后200ms却冲到0.5。这就是插值器在起作用。
我个人习惯把插值器理解为「节奏控制器」。它不关心你最终要变成什么值,只关心「时间进度」和「数值进度」之间的映射关系。
插值器输入:时间百分比 t (0~1)
插值器输出:数值百分比 f(t) (0~1)
估值器拿到 f(t) 后,再算出具体数值
系统自带的插值器
Android系统给我们准备了不少现成的插值器。我整理了一张表,方便你对照着看:
| 插值器类名 | XML资源ID | 效果描述 |
|---|---|---|
| LinearInterpolator | @android:anim/linear_interpolator | 匀速运动,最基础 |
| AccelerateInterpolator | @android:anim/accelerate_interpolator | 先慢后快,像起步加速 |
| DecelerateInterpolator | @android:anim/decelerate_interpolator | 先快后慢,像刹车减速 |
| AccelerateDecelerateInterpolator | @android:anim/accelerate_decelerate_interpolator | 两头慢中间快,像过山车 |
| BounceInterpolator | @android:anim/bounce_interpolator | 终点处弹跳几下 |
| OvershootInterpolator | @android:anim/overshoot_interpolator | 冲过终点再回弹 |
| AnticipateInterpolator | @android:anim/anticipate_interpolator | 先后退再前进 |
| AnticipateOvershootInterpolator | @android:anim/anticipate_overshoot_interpolator | 先后退,再冲过终点回弹 |
| CycleInterpolator | @android:anim/cycle_interpolator | 正弦曲线循环运动 |
这些插值器怎么用?在XML动画文件里直接指定就行:
<scale
android:fromXScale="1.0"
android:toXScale="1.5"
android:fromYScale="1.0"
android:toYScale="1.5"
android:pivotX="50%"
android:pivotY="50%"
android:duration="1000"
android:interpolator="@android:anim/bounce_interpolator" />
代码里设置也简单:
Animation anim = AnimationUtils.loadAnimation(context, R.anim.scale_anim);
anim.setInterpolator(new BounceInterpolator());
view.startAnimation(anim);
自定义插值器
系统提供的插值器虽然多,但总有不够用的时候。我记得有个项目要做「弹簧松手回弹」的效果,系统自带的都不太像。这时候就得自己动手了。
自定义插值器只需要实现 Interpolator 接口,重写 getInterpolation(float input) 方法。input是时间进度(0~1),返回值是数值进度(0~1)。
来看一个我实际用过的「弹性插值器」:
public class SpringInterpolator implements Interpolator {
private float tension;
public SpringInterpolator(float tension) {
this.tension = tension;
}
@Override
public float getInterpolation(float input) {
// 模拟弹簧阻尼振荡
return (float) (1 - Math.exp(-tension * input) *
Math.cos(10 * input * Math.PI));
}
}
这个插值器的效果是:物体到达终点后会轻微振荡几次,然后稳定下来。tension值越大,振荡衰减越快。
估值器:TypeEvaluator
插值器算出了数值进度,接下来估值器就要把这个进度转换成具体的属性值。
比如一个View从x=0移动到x=100,插值器告诉你现在走了60%,估值器就要算出具体数值:0 + (100-0) * 60% = 60。
系统自带的估值器有:
- IntEvaluator:计算int类型值
- FloatEvaluator:计算float类型值
- ArgbEvaluator:计算颜色值(ARGB)
- RectEvaluator:计算矩形区域
但如果你要动画的对象不是这些基本类型,就得自定义估值器了。
自定义TypeEvaluator
我记得有个需求:让一个点沿着贝塞尔曲线运动。系统没有现成的估值器,只能自己写。
自定义估值器需要实现 TypeEvaluator<T> 接口,重写 evaluate(float fraction, T startValue, T endValue) 方法。
来看一个自定义Point估值器的例子:
public class PointEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
float x = startValue.x + (endValue.x - startValue.x) * fraction;
float y = startValue.y + (endValue.y - startValue.y) * fraction;
return new Point((int) x, (int) y);
}
}
使用的时候:
ObjectAnimator anim = ObjectAnimator.ofObject(
view,
"position",
new PointEvaluator(),
new Point(0, 0),
new Point(300, 500)
);
anim.setDuration(2000);
anim.start();
插值器与估值器的协作流程
为了让你更直观地理解它们的关系,我画了一张流程图:
流程其实很简单:
- 动画引擎计算当前时间进度 t(0~1)
- 把 t 交给插值器,得到数值进度 f(t)
- 把 f(t) 和起始值、结束值交给估值器,算出具体属性值
- 更新View的属性,触发重绘
- 重复以上步骤,直到动画结束
实际项目中的选择建议
说了这么多,到底什么时候用哪个?我根据经验总结了几条:
- 普通UI动效:AccelerateDecelerateInterpolator 最自然,大部分场景都适用
- 列表条目入场:DecelerateInterpolator 配合一点延迟,效果很顺滑
- 按钮点击反馈:OvershootInterpolator 或 BounceInterpolator 能增加趣味性
- 物理模拟:必须自定义插值器,系统提供的都不够真实
- 颜色渐变:用 ArgbEvaluator,别自己写颜色插值,容易出问题
嗯,插值器和估值器这部分内容就这些。理解它们的工作原理,你就能掌控View动画的每一个细节。后面讲到属性动画和Compose动画时,你会发现这些概念依然在底层发挥作用——只是换了个更优雅的写法而已。