5. 值计算管线详解(一帧调用栈)
本节围绕一次 vsync 帧到达时,从 AnimationHandler 到最终计算出动画值的完整调用栈,结合前述状态字段和时间变量,逐函数解析。
5.1 调用栈总览
AnimationHandler.doAnimationFrame(frameTime) ← 遍历所有活跃 Animator
│
└─ ValueAnimator.doAnimationFrame(frameTime) ← [1] 帧入口:时间与状态处理
│
├─ startAnimation() ← [2] 首帧启动(条件触发)
│
└─ animateBasedOnTime(currentTime) ← [3] 时间→总 fraction
│
├─ notifyListeners(ON_REPEAT) ← [4] 重复通知(条件触发)
│
└─ getCurrentIterationFraction(fraction) ← [5] 总 fraction→迭代 fraction
│
├─ getCurrentIteration(fraction) ← [6] 拆分迭代号
│
└─ shouldPlayBackward(iteration) ← [7] 判断方向
│
└─ animateValue(iterFraction) ← [8] 核心值计算
│
├─ mInterpolator.getInterpolation()← [9] 节奏变换
│
├─ mValues[i].calculateValue() ← [10] 类型求值
│ │
│ └─ mKeyframes.getValue() ← [11] 关键帧插值
│ │
│ └─ mEvaluator.evaluate() ← [12] Evaluator 线性插值
│
└─ 通知 mUpdateListeners ← [13] 回调
5.2 1 doAnimationFrame(frameTime) --- 帧入口
这是 AnimationHandler 遍历 mAnimationCallbacks 时对每个 Animator 调用的入口。负责处理所有与时间、状态相关的前置逻辑。
执行流程:
doAnimationFrame(frameTime):
步骤 1:初始化 mStartTime(首帧)
java
if (mStartTime < 0) { // 首帧
mStartTime = mReversing
? frameTime // 反向播放:startDelay 放在末尾
: frameTime + (long)(mStartDelay * resolveDurationScale()); // 正向:startDelay 加到起始
}
mStartTime < 0(初始值-1)表示首次收到帧回调- 正向播放时,
mStartTime= 帧时间 + 缩放后的 startDelay,在此时间之前动画不计算值 - 反向播放时,
mStartTime= 帧时间,startDelay 逻辑在动画末尾处理
步骤 2:处理 pause/resume
java
if (mPaused) {
mPauseTime = frameTime; // 记录暂停时刻
removeAnimationCallback(); // 从 AnimationHandler 注销,不再收帧
return false;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
mStartTime += (frameTime - mPauseTime); // 偏移 startTime,跳过暂停期间
}
}
- 暂停时记录
mPauseTime并注销回调,动画"冻结" - 恢复时将
mStartTime向后偏移(frameTime - mPauseTime),等效于暂停期间时间不流逝
步骤 3:处理 startDelay 等待期
java
if (!mRunning) {
if (mStartTime > frameTime && mSeekFraction == -1) {
return false; // 还在 startDelay 中,跳过本帧
} else {
mRunning = true;
startAnimation(); // startDelay 结束,正式开始
}
}
mRunning == false且mStartTime > frameTime:仍在延迟等待中,直接返回- 延迟结束后:设置
mRunning = true,调用startAnimation()通知监听器
步骤 4:处理 Seek(首帧)
java
if (mLastFrameTime < 0) { // 首帧(与上面的首帧不同,这是进入帧循环的首帧)
if (mSeekFraction >= 0) {
long seekTime = (long)(getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime; // 将 startTime 前移,使 fraction 对齐到 seek 位置
mSeekFraction = -1;
}
mStartTimeCommitted = false; // 允许后续 jank 补偿
}
mLastFrameTime = frameTime;
步骤 5:计算值并判断结束
java
final long currentTime = Math.max(frameTime, mStartTime); // 防止负时间(seek 回退时)
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
return finished;
5.3 2 startAnimation() --- 首帧启动
仅在 startDelay 结束、动画正式开始时调用一次。
java
private void startAnimation() {
mAnimationEndRequested = false;
initAnimation(); // 初始化 PropertyValuesHolder(分配 Evaluator)
mRunning = true;
mOverallFraction = (mSeekFraction >= 0) ? mSeekFraction : 0f;
notifyStartListeners(mReversing); // 通知 onAnimationStart
}
initAnimation()调用PropertyValuesHolder.init(),为每个mValues[i]分配默认 Evaluator(Int/Float)并传递给 KeyframeSetmOverallFraction初始化为 seek 位置或 0
5.4 3 animateBasedOnTime(currentTime) --- 时间→总 fraction
这是从时间到 fraction 的核心转换,同时处理重复检测和结束判断。
java
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
final long scaledDuration = getScaledDuration();
// ── 时间 → 总 fraction ──
final float fraction = scaledDuration > 0
? (float)(currentTime - mStartTime) / scaledDuration
: 1f; // 0 时长直接跳到结束
final float lastFraction = mOverallFraction;
// ── 重复检测 ──
final boolean newIteration = (int)fraction > (int)lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1)
&& (mRepeatCount != INFINITE);
if (scaledDuration == 0) {
done = true; // 0 时长,直接结束
} else if (newIteration && !lastIterationFinished) {
notifyListeners(AnimatorCaller.ON_REPEAT, false); // 进入新迭代
} else if (lastIterationFinished) {
done = true; // 所有迭代完成
}
mOverallFraction = clampFraction(fraction);
// ── 总 fraction → 迭代 fraction → 值 ──
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
关键变量说明:
| 变量 | 含义 | 示例 |
|---|---|---|
fraction |
总进度 = 已过时间 / 缩放时长 | 2.3 = 第 2 次重复,进度 30% |
lastFraction |
上一帧的总进度,用于检测迭代切换 | 0.9 → 1.1 说明进入了新迭代 |
mOverallFraction |
当前保存的总进度(clamp 后) | clamp 到 [0, mRepeatCount+1] |
newIteration |
(int)fraction > (int)lastFraction |
0.9→1.1:0 < 1 = true |
lastIterationFinished |
fraction >= mRepeatCount + 1 |
repeatCount=2,fraction≥3 = 结束 |
5.5 567 getCurrentIterationFraction --- 总 fraction → 迭代 fraction
这三个函数协作将总 fraction(可能 >1)转换为 [0,1] 的当前迭代 fraction,同时处理 REVERSE 模式的方向交替。
getCurrentIterationFraction(fraction, inReverse)
java
private float getCurrentIterationFraction(float fraction, boolean inReverse) {
fraction = clampFraction(fraction); // 限制到 [0, mRepeatCount+1]
int iteration = getCurrentIteration(fraction); // 拆分迭代号
float currentFraction = fraction - iteration; // 迭代内进度 ∈ [0,1]
return shouldPlayBackward(iteration, inReverse)
? 1f - currentFraction // 反向:1→0
: currentFraction; // 正向:0→1
}
getCurrentIteration(fraction)
java
private int getCurrentIteration(float fraction) {
double iteration = Math.floor(fraction);
// 特殊处理:fraction 恰好为整数时,视为上一个迭代的结束
if (fraction == iteration && fraction > 0) {
iteration--;
}
return (int) iteration;
}
示例 :fraction = 2.0 → iteration = 1,currentFraction = 1.0(第 1 次迭代的末尾,而非第 2 次的开头)。
shouldPlayBackward(iteration, inReverse)
java
private boolean shouldPlayBackward(int iteration, boolean inReverse) {
if (iteration > 0 && mRepeatMode == REVERSE
&& (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
if (inReverse) {
return (iteration % 2) == 0; // 反向启动时:偶数迭代反向
} else {
return (iteration % 2) != 0; // 正向启动时:奇数迭代反向
}
} else {
return inReverse; // 非逆序模式或第 0 次迭代
}
}
5.6 Repeat 与 Reverse 原理
Repeat 机制
Repeat 通过总 fraction 超过 1 来驱动。mOverallFraction 可以大于 1,整数部分代表已完成的迭代数,小数部分代表当前迭代进度。
mOverallFraction = 2.35
│
├─ iteration = 2 (已完成 2 次迭代,正在第 3 次)
└─ iterFraction = 0.35 (当前迭代进度 35%)
mRepeatCount = 0:不重复,fractionclamp 到[0, 1]mRepeatCount = 3:fractionclamp 到[0, 4],共播放 4 次mRepeatCount = INFINITE:不设上界
RESTART 模式 :每次迭代都从 0→1,shouldPlayBackward 始终返回 inReverse(正向时为 false)。
REVERSE 模式:奇偶迭代方向交替:
| iteration | shouldPlayBackward(正向启动) | 实际方向 | fraction 变化 |
|---|---|---|---|
| 0 | false | 正向 | 0 → 1 |
| 1 | true | 反向 | 1 → 0 |
| 2 | false | 正向 | 0 → 1 |
| 3 | true | 反向 | 1 → 0 |
Reverse 机制
reverse() 方法翻转动画方向,通过修改 mReversing 标志和 mStartTime 实现:
java
public void reverse() {
if (isPulsingInternal()) {
// 运行中:计算剩余时间,将 startTime 对齐到"从另一端开始"
long currentTime = AnimationUtils.currentAnimationTimeMillis();
long currentPlayTime = currentTime - mStartTime;
long timeLeft = getScaledDuration() - currentPlayTime;
mStartTime = currentTime - timeLeft; // 使 fraction 翻转
mStartTimeCommitted = true;
mReversing = !mReversing;
} else if (mStarted) {
mReversing = !mReversing;
end(); // 已 start 但未运行,直接结束
} else {
start(true); // 未 start,以反向模式启动
}
}
mReversing 传入 getCurrentIterationFraction 的 inReverse 参数,影响 shouldPlayBackward 的计算,从而决定每个迭代的播放方向。
reverse + REVERSE repeatMode 的组合 :mReversing = true 时,shouldPlayBackward 的奇偶判断取反,导致迭代的实际方向与 mReversing = false 时完全相反。
5.7 8 animateValue(fraction) --- 核心值计算
java
void animateValue(float fraction) {
// [9] 节奏变换
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction; // 保存插值后 fraction(供 getAnimatedFraction() 查询)
// [10] 类型求值:对每个 PropertyValuesHolder 计算值
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
// [13] 通知 UpdateListener
if (mSeekFraction >= 0 || mStartListenersCalled) {
callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false);
}
}
注意 :传入 animateValue 的 fraction 已经是 [0,1] 范围的迭代 fraction。mInterpolator 在此对其进行非线性映射,输出可能超出 [0,1](如过冲、弹跳)。
5.8 101112 calculateValue → getValue → evaluate --- 值计算链
PropertyValuesHolder.calculateValue(fraction)
│
└─ mKeyframes.getValue(fraction) ← KeyframeSet 查找区间并插值
│
├─ 定位区间 [prevKeyframe, nextKeyframe]
│
├─ 计算 intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction)
│
├─ 应用局部 Interpolator: intervalFraction = nextKf.getInterpolator().getInterpolation(intervalFraction)
│ (若该 Keyframe 未设置 Interpolator,则使用线性)
│
└─ mEvaluator.evaluate(intervalFraction, prevValue, nextValue)
│
└─ result = startValue + fraction * (endValue - startValue) ← 线性插值
IntKeyframeSet / FloatKeyframeSet 的优化 :重写 getIntValue() / getFloatValue(),直接使用原始类型计算,避免装箱开销。当 mEvaluator == null 时使用内置线性公式,否则调用 Evaluator。
最终 :计算结果存入 PropertyValuesHolder.mAnimatedValue,ObjectAnimator 通过 setAnimatedValue(target) 将值设置到目标对象。
6. Seek 机制
通过 setCurrentPlayTime() / setCurrentFraction() 跳转到指定位置:
- 动画运行中 :直接修改
mStartTime = currentTime - seekTime - 未运行时 :设置
mSeekFraction,在首帧时应用(doAnimationFrame第1556-1559行)
7. Jank 补偿
首帧可能因调度延迟而产生 jank:
- 首帧设置
mStartTimeCommitted = false(第1561行) commitAnimationFrame(frameTime)被调用时,计算adjustment = frameTime - mLastFrameTime- 若
adjustment > 0,修正mStartTime += adjustment(第1363行) - 设置
mStartTimeCommitted = true,后续不再补偿
8. 事件通知
8.1 生命周期监听器(继承自 Animator)
| 事件 | 时机 |
|---|---|
onAnimationStart |
startAnimation() 调用时(考虑 startDelay) |
onAnimationEnd |
endAnimation() 调用时 |
onAnimationCancel |
cancel() 调用时 |
onAnimationRepeat |
新迭代开始时 |
8.2 帧更新监听器
java
public interface AnimatorUpdateListener {
void onAnimationUpdate(ValueAnimator animation);
}
在 animateValue() 中,每次值计算完成后调用(第1650-1652行)。
9. AnimatorSet 集成
ValueAnimator 提供了供 AnimatorSet 驱动的内部方法:
| 方法 | 用途 |
|---|---|
animateValuesInRange() |
在指定时间范围内计算动画值(支持跨迭代) |
animateSkipToEnds() |
跳过到起始/结束值 |
pulseAnimationFrame() |
由父 AnimatorSet 驱动帧(mSelfPulse = false 时) |
startWithoutPulsing() |
启动但不自行注册帧回调 |
10. 线程模型
- 必须 在拥有
Looper的线程调用start()/cancel()/end()/resume() - 所有帧回调在调用
start()的线程执行 currentAnimationTimeMillis()基于ThreadLocal,每个线程独立维护动画时钟
11. 工厂方法
| 方法 | 值类型 | 默认 Evaluator |
|---|---|---|
ofInt(int...) |
int | IntEvaluator |
ofArgb(int...) |
int (color) | ArgbEvaluator |
ofFloat(float...) |
float | FloatEvaluator |
ofObject(TypeEvaluator, Object...) |
任意对象 | 自定义 |
ofPropertyValuesHolder(PropertyValuesHolder...) |
多属性并行 | 各自指定 |
12. Clone 机制
clone() 执行深拷贝:
- 复制
mUpdateListeners列表 - 重置所有运行时状态(时间、fraction、标志位)
- 深拷贝
PropertyValuesHolder[]及其mValuesMap