8、插值器与估值器协同:TimeInterpolator与TypeEvaluator配合原理、自定义贝塞尔曲线插值器
动画的本质是什么?说白了,就是在一段时间内,让某个值从起点变到终点。但这里有个关键问题——怎么变?是匀速变,还是先快后慢,还是像弹簧一样来回弹?
这就引出了我们今天的主角:插值器(TimeInterpolator)和估值器(TypeEvaluator)。这两个家伙是Android动画引擎的「左右护法」,一个管节奏,一个管数值。我刚开始做动画时,总觉得它们是一回事,后来踩了坑才明白——分工完全不同。
8.1 它们到底怎么配合的?
先看一张图,你就明白了:
你看,整个流程非常清晰。我当年第一次看源码时,发现ValueAnimator内部就是这么干的——先调插值器,再调估值器,各司其职。
8.2 源码层面看配合
咱们直接看ValueAnimator的核心逻辑,其实就几行关键代码:
// ValueAnimator 内部核心逻辑(简化版)
void animateValue(float fraction) {
// 第一步:插值器处理时间进度
float interpolatedFraction = mInterpolator.getInterpolation(fraction);
// 第二步:估值器计算具体数值
mCurrentValue = mEvaluator.evaluate(interpolatedFraction,
mStartValue, mEndValue);
// 第三步:通知监听器
onAnimationUpdate(mCurrentValue);
}
嗯,这里要注意:fraction是引擎传进来的时间进度(0~1),但经过插值器后,interpolatedFraction可能就不是线性了。比如你用了AccelerateInterpolator,那interpolatedFraction在前期会小于fraction,后期才追上来。
fraction 和 interpolatedFraction 的差值。如果差值太大,说明动画节奏有问题,容易造成视觉上的「突兀感」。
8.3 系统自带的插值器与估值器
Android 给我们准备了不少现成的工具,我列个表方便你查阅:
| 类型 | 类名 | 效果 | 适用场景 |
|---|---|---|---|
| 插值器 | LinearInterpolator | 匀速 | 机械运动、进度条 |
| AccelerateInterpolator | 先慢后快 | 物体掉落、弹出 | |
| DecelerateInterpolator | 先快后慢 | 物体停止、淡入 | |
| BounceInterpolator | 末尾弹跳 | 消息提示、小球落地 | |
| 估值器 | IntEvaluator | 整数线性插值 | 位置、大小 |
| FloatEvaluator | 浮点数线性插值 | 透明度、旋转角度 | |
| ArgbEvaluator | 颜色平滑过渡 | 背景色、文字色变化 |
你想想看,如果只是做简单的位移动画,用LinearInterpolator加FloatEvaluator就够了。但如果你想做那种「卡片飞入时带点弹性效果」,就得自己动手了。
8.4 自定义贝塞尔曲线插值器
说到自定义插值器,我最常用的就是贝塞尔曲线。为什么?因为它的控制点可以精确调节「加速-减速」的节奏,比系统自带的灵活太多。
我在项目中做过一个「下拉刷新」的动画,要求头部先快速弹出一段,然后慢悠悠地归位。用系统插值器怎么调都不对味,最后用三阶贝塞尔曲线搞定了。
先看代码:
class BezierInterpolator(
private val controlX1: Float = 0.25f,
private val controlY1: Float = 0.1f,
private val controlX2: Float = 0.25f,
private val controlY2: Float = 1.0f
) : TimeInterpolator {
override fun getInterpolation(input: Float): Float {
// 使用二分法逼近贝塞尔曲线上的 t 值
var t = 0.5f
var low = 0.0f
var high = 1.0f
// 迭代 10 次,精度足够
for (i in 0 until 10) {
val x = cubicBezierX(t, controlX1, controlX2)
if (x < input) {
low = t
} else {
high = t
}
t = (low + high) / 2
}
// 用逼近的 t 计算 y 值
return cubicBezierY(t, controlY1, controlY2)
}
private fun cubicBezierX(t: Float, c1: Float, c2: Float): Float {
return 3 * (1 - t) * (1 - t) * t * c1 +
3 * (1 - t) * t * t * c2 +
t * t * t
}
private fun cubicBezierY(t: Float, c1: Float, c2: Float): Float {
return 3 * (1 - t) * (1 - t) * t * c1 +
3 * (1 - t) * t * t * c2 +
t * t * t
}
}
input 作为 t 值去算 y,结果动画完全不对。后来才意识到——贝塞尔曲线的 x 和 y 是参数方程,input 是 x 轴上的进度,必须先反解出 t,再用 t 算 y。这个「反解」步骤不能省!
使用起来也很简单:
// 使用贝塞尔插值器,模拟「先快后慢再快」的效果
val interpolator = BezierInterpolator(
controlX1 = 0.42f, // 控制点1 x
controlY1 = 0.0f, // 控制点1 y
controlX2 = 0.58f, // 控制点2 x
controlY2 = 1.0f // 控制点2 y
)
val animator = ValueAnimator.ofFloat(0f, 1f).apply {
this.interpolator = interpolator
duration = 300
addUpdateListener { animator ->
val value = animator.animatedValue as Float
// 更新 UI
view.translationX = value * 200
}
}
你可能会问:控制点怎么选?我一般用这个经验:
- 先快后慢:控制点1靠近(0,0),控制点2靠近(1,0) — 比如 (0.25, 0.1, 0.25, 1.0)
- 先慢后快:控制点1靠近(0,1),控制点2靠近(1,1) — 比如 (0.0, 0.0, 0.58, 1.0)
- 弹性效果:控制点1的y大于1,或者控制点2的y小于0 — 比如 (0.25, 1.5, 0.75, -0.5)
8.5 自定义估值器:不只是线性
说完了插值器,再聊聊估值器。系统自带的FloatEvaluator和IntEvaluator都是线性插值,但有时候我们需要非线性——比如颜色变化时,想让红色先变、蓝色后变。
来看一个自定义估值器的例子:
class SpringEvaluator(private val stiffness: Float = 0.5f) : TypeEvaluator<Float> {
override fun evaluate(fraction: Float, startValue: Float, endValue: Float): Float {
// 在标准线性插值基础上,加入弹簧阻尼效果
val linear = startValue + (endValue - startValue) * fraction
val spring = Math.sin(fraction * Math.PI * 4).toFloat() *
(1 - fraction) * stiffness * (endValue - startValue)
return linear + spring
}
}
这个估值器会在线性插值的基础上叠加一个正弦波衰减,模拟出「弹簧回弹」的效果。配合DecelerateInterpolator使用,效果更自然。
evaluate(0, start, end) == start 且 evaluate(1, start, end) == end。否则动画结束位置不对,UI 会「跳一下」。
8.6 插值器与估值器的协同调优
最后分享一个实战经验。有一次我做「点赞动画」,要求心形图标先放大再缩小,最后弹跳两下。我试了各种组合:
- AccelerateDecelerateInterpolator + FloatEvaluator:太平淡,没有「弹」的感觉
- BounceInterpolator + FloatEvaluator:弹得太夸张,像皮球
- 自定义贝塞尔插值器 + SpringEvaluator:完美!既有加速减速,又有轻微回弹
说白了,插值器控制「节奏感」,估值器控制「数值变化曲线」。两者配合得好,动画才有「灵魂」。我现在的习惯是:先确定插值器(决定动画的「性格」),再微调估值器(决定动画的「细节」)。
嗯,这一章的内容就到这。记住一句话:插值器是「什么时候变」,估值器是「变成什么」。搞懂这个,你就能随心所欲地控制动画了。
公众号:蓝海资料掘金营,微信deep3321