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

【阅读源码--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(首帧)

if(mStartTime<0){// 首帧mStartTime=mReversing?frameTime// 反向播放:startDelay 放在末尾:frameTime+(long)(mStartDelay*resolveDurationScale());// 正向:startDelay 加到起始}
  • mStartTime < 0(初始值-1)表示首次收到帧回调
  • 正向播放时,mStartTime= 帧时间 + 缩放后的 startDelay,在此时间之前动画不计算值
  • 反向播放时,mStartTime= 帧时间,startDelay 逻辑在动画末尾处理

步骤 2:处理 pause/resume

if(mPaused){mPauseTime=frameTime;// 记录暂停时刻removeAnimationCallback();// 从 AnimationHandler 注销,不再收帧returnfalse;}elseif(mResumed){mResumed=false;if(mPauseTime>0){mStartTime+=(frameTime-mPauseTime);// 偏移 startTime,跳过暂停期间}}
  • 暂停时记录mPauseTime并注销回调,动画"冻结"
  • 恢复时将mStartTime向后偏移(frameTime - mPauseTime),等效于暂停期间时间不流逝

步骤 3:处理 startDelay 等待期

if(!mRunning){if(mStartTime>frameTime&&mSeekFraction==-1){returnfalse;// 还在 startDelay 中,跳过本帧}else{mRunning=true;startAnimation();// startDelay 结束,正式开始}}
  • mRunning == falsemStartTime > frameTime:仍在延迟等待中,直接返回
  • 延迟结束后:设置mRunning = true,调用startAnimation()通知监听器

步骤 4:处理 Seek(首帧)

if(mLastFrameTime<0){// 首帧(与上面的首帧不同,这是进入帧循环的首帧)if(mSeekFraction>=0){longseekTime=(long)(getScaledDuration()*mSeekFraction);mStartTime=frameTime-seekTime;// 将 startTime 前移,使 fraction 对齐到 seek 位置mSeekFraction=-1;}mStartTimeCommitted=false;// 允许后续 jank 补偿}mLastFrameTime=frameTime;

步骤 5:计算值并判断结束

finallongcurrentTime=Math.max(frameTime,mStartTime);// 防止负时间(seek 回退时)booleanfinished=animateBasedOnTime(currentTime);if(finished){endAnimation();}returnfinished;

5.3 [2] startAnimation() — 首帧启动

仅在 startDelay 结束、动画正式开始时调用一次。

privatevoidstartAnimation(){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 的核心转换,同时处理重复检测和结束判断。

booleananimateBasedOnTime(longcurrentTime){booleandone=false;if(mRunning){finallongscaledDuration=getScaledDuration();// ── 时间 → 总 fraction ──finalfloatfraction=scaledDuration>0?(float)(currentTime-mStartTime)/scaledDuration:1f;// 0 时长直接跳到结束finalfloatlastFraction=mOverallFraction;// ── 重复检测 ──finalbooleannewIteration=(int)fraction>(int)lastFraction;finalbooleanlastIterationFinished=(fraction>=mRepeatCount+1)&&(mRepeatCount!=INFINITE);if(scaledDuration==0){done=true;// 0 时长,直接结束}elseif(newIteration&&!lastIterationFinished){notifyListeners(AnimatorCaller.ON_REPEAT,false);// 进入新迭代}elseif(lastIterationFinished){done=true;// 所有迭代完成}mOverallFraction=clampFraction(fraction);// ── 总 fraction → 迭代 fraction → 值 ──floatcurrentIterationFraction=getCurrentIterationFraction(mOverallFraction,mReversing);animateValue(currentIterationFraction);}returndone;}

关键变量说明

变量含义示例
fraction总进度 = 已过时间 / 缩放时长2.3 = 第 2 次重复,进度 30%
lastFraction上一帧的总进度,用于检测迭代切换0.9 → 1.1 说明进入了新迭代
mOverallFraction当前保存的总进度(clamp 后)clamp 到[0, mRepeatCount+1]
newIteration(int)fraction > (int)lastFraction0.9→1.1:0 < 1= true
lastIterationFinishedfraction >= mRepeatCount + 1repeatCount=2,fraction≥3 = 结束

5.5 [5][6][7] getCurrentIterationFraction — 总 fraction → 迭代 fraction

这三个函数协作将总 fraction(可能 >1)转换为[0,1]的当前迭代 fraction,同时处理 REVERSE 模式的方向交替。

getCurrentIterationFraction(fraction, inReverse)
privatefloatgetCurrentIterationFraction(floatfraction,booleaninReverse){fraction=clampFraction(fraction);// 限制到 [0, mRepeatCount+1]intiteration=getCurrentIteration(fraction);// 拆分迭代号floatcurrentFraction=fraction-iteration;// 迭代内进度 ∈ [0,1]returnshouldPlayBackward(iteration,inReverse)?1f-currentFraction// 反向:1→0:currentFraction;// 正向:0→1}
getCurrentIteration(fraction)
privateintgetCurrentIteration(floatfraction){doubleiteration=Math.floor(fraction);// 特殊处理:fraction 恰好为整数时,视为上一个迭代的结束if(fraction==iteration&&fraction>0){iteration--;}return(int)iteration;}

示例fraction = 2.0iteration = 1currentFraction = 1.0(第 1 次迭代的末尾,而非第 2 次的开头)。

shouldPlayBackward(iteration, inReverse)
privatebooleanshouldPlayBackward(intiteration,booleaninReverse){if(iteration>0&&mRepeatMode==REVERSE&&(iteration<(mRepeatCount+1)||mRepeatCount==INFINITE)){if(inReverse){return(iteration%2)==0;// 反向启动时:偶数迭代反向}else{return(iteration%2)!=0;// 正向启动时:奇数迭代反向}}else{returninReverse;// 非逆序模式或第 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 = 3fractionclamp 到[0, 4],共播放 4 次
  • mRepeatCount = INFINITE:不设上界

RESTART 模式:每次迭代都从 0→1,shouldPlayBackward始终返回inReverse(正向时为 false)。

REVERSE 模式:奇偶迭代方向交替:

iterationshouldPlayBackward(正向启动)实际方向fraction 变化
0false正向0 → 1
1true反向1 → 0
2false正向0 → 1
3true反向1 → 0
Reverse 机制

reverse()方法翻转动画方向,通过修改mReversing标志和mStartTime实现:

publicvoidreverse(){if(isPulsingInternal()){// 运行中:计算剩余时间,将 startTime 对齐到"从另一端开始"longcurrentTime=AnimationUtils.currentAnimationTimeMillis();longcurrentPlayTime=currentTime-mStartTime;longtimeLeft=getScaledDuration()-currentPlayTime;mStartTime=currentTime-timeLeft;// 使 fraction 翻转mStartTimeCommitted=true;mReversing=!mReversing;}elseif(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) — 核心值计算

voidanimateValue(floatfraction){// [9] 节奏变换fraction=mInterpolator.getInterpolation(fraction);mCurrentFraction=fraction;// 保存插值后 fraction(供 getAnimatedFraction() 查询)// [10] 类型求值:对每个 PropertyValuesHolder 计算值for(inti=0;i<numValues;++i){mValues[i].calculateValue(fraction);}// [13] 通知 UpdateListenerif(mSeekFraction>=0||mStartListenersCalled){callOnList(mUpdateListeners,AnimatorCaller.ON_UPDATE,this,false);}}

注意:传入animateValue的 fraction 已经是[0,1]范围的迭代 fraction。mInterpolator在此对其进行非线性映射,输出可能超出[0,1](如过冲、弹跳)。

5.8 [10][11][12] 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

事件时机
onAnimationStartstartAnimation()调用时(考虑 startDelay)
onAnimationEndendAnimation()调用时
onAnimationCancelcancel()调用时
onAnimationRepeat新迭代开始时

8.2 帧更新监听器

publicinterfaceAnimatorUpdateListener{voidonAnimationUpdate(ValueAnimatoranimation);}

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...)intIntEvaluator
ofArgb(int...)int (color)ArgbEvaluator
ofFloat(float...)floatFloatEvaluator
ofObject(TypeEvaluator, Object...)任意对象自定义
ofPropertyValuesHolder(PropertyValuesHolder...)多属性并行各自指定

12. Clone 机制

clone()执行深拷贝:

  • 复制mUpdateListeners列表
  • 重置所有运行时状态(时间、fraction、标志位)
  • 深拷贝PropertyValuesHolder[]及其mValuesMap