【阅读源码--Android】动画之ValueAnimator--2

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 == falsemStartTime > 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)并传递给 KeyframeSet
  • mOverallFraction 初始化为 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.0iteration = 1currentFraction = 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:不重复,fraction clamp 到 [0, 1]
  • mRepeatCount = 3fraction clamp 到 [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 传入 getCurrentIterationFractioninReverse 参数,影响 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.mAnimatedValueObjectAnimator 通过 setAnimatedValue(target) 将值设置到目标对象。

6. Seek 机制

通过 setCurrentPlayTime() / setCurrentFraction() 跳转到指定位置:

  • 动画运行中 :直接修改 mStartTime = currentTime - seekTime
  • 未运行时 :设置 mSeekFraction,在首帧时应用(doAnimationFrame 第1556-1559行)

7. Jank 补偿

首帧可能因调度延迟而产生 jank:

  1. 首帧设置 mStartTimeCommitted = false(第1561行)
  2. commitAnimationFrame(frameTime) 被调用时,计算 adjustment = frameTime - mLastFrameTime
  3. adjustment > 0,修正 mStartTime += adjustment(第1363行)
  4. 设置 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
相关推荐
知行合一。。。1 小时前
DeepAgents--01--Agent和OpenClaw的相关概念
android·数据库
汪海游龙2 小时前
告别 Play Console 手动上传:从模拟器截图到自动发版的完整流水线
android·ci/cd·github
weixin_440784112 小时前
【IntentSeivice实现原理】
android·java·开发语言·intentservice
delta_hell3 小时前
【阅读源码--Android】动画之辅助类
android·源码·animator
消失的旧时光-19437 小时前
第 2 篇:Android 控制屏为什么与机器人主控使用 TCP 长连接?
android·tcp/ip·机器人
小Ti客栈7 小时前
MySQL查询原理:从Server到InnoDB
android·mysql·adb
纪念 2298 小时前
MySQL(题目讲解二)
android·数据库·mysql
你听得到119 小时前
排查 App 问题:别只盯着报错,把前后发生的事情串起来
android·前端·flutter
淡淡的香烟9 小时前
Android智能猫砂盆视频加载慢卡顿问题分析
android·服务器·音视频