本文第一章主要介绍两个与动画关联颇深的 Chreographer 和 Interpolator,并就相关原理进行了叙述。第二章对 Android 三种动画原理进行分析,侧重分析了每一帧动画的绘制流程。
一、开始之前
1.1 Choreographer
Chreographer是 Framework 层进行处理绘制的控制类,它会在每次屏幕刷新信号来临时,执行与绘制相关的过程。
Choreographer 为调用线程分配一个 Choreographer 单例的实例,调用线程必须有消息队列,ViewRootImpl 是在 UI 线程上执行的,因此创建的 Choreographer 使用的是 UI 线程消息队列。
// ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
...
mChoreographer = Choreographer.getInstance();
...
}
// Choreographer.java
public static Choreographer getInstance() {
// 为调用线程分配一个 Choreographer 单例的实例
return sThreadInstance.get();
}
private static final ThreadLocal<Choreographer> sThreadInstance =
new ThreadLocal<Choreographer>() {
@Override
protected Choreographer initialValue() {
// 调用线程必须有消息队列
Looper looper = Looper.myLooper();
if (looper == null) {
throw new IllegalStateException("The current thread must have a looper!");
}
return new Choreographer(looper, VSYNC_SOURCE_APP);
}
};
// Choreographer.java
// 订阅下一次屏幕刷新信号
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
// 使用 VSYNC 机制
// private static final boolean USE_VSYNC = SystemProperties.getBoolean("debug.choreographer.vsync", true); 通过读取系统属性获取值
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
// 当前运行在创建 Choreographer 线程,直接请求 VSYNC 信号
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
// 否则通过创建 Choreographer 的线程来请求 VSYNC 信号,创建的线程必须有消息队列
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
// 没有使用 VSYNC 机制,使用异步消息延时执行屏幕刷新
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
为了兼容没有 VSYNC 机制的设备,通过延时消息实现屏幕刷新通知,这也就解释了为什么创建 Choreographer 的线程必须要有消息队列了。
如果有 VSYNC 机制则直接请求信号或者通过延时消息,获取到的屏幕刷新信号后都会执行到 doFrame() 执行绘制当前帧。
void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
//...
//当前帧的vsync信号来的时间,假如为5分200ms
long intendedFrameTimeNanos = frameTimeNanos;
//当前时间,也就是开始绘制的时间,假如为5分150ms
startNanos = System.nanoTime();
//计算时间差,如果大于一个帧时间,则是跳帧了。比如是50ms,大于16ms
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= mFrameIntervalNanos) {
//计算掉了几帧,50/16=3帧
final long skippedFrames = jitterNanos / mFrameIntervalNanos;
//计算一帧内时间差,50%16=2ms
final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
//修正时间,vsync信号应该来得时间,为5分148ms,保证和绘制时间对应上
frameTimeNanos = startNanos - lastFrameOffset;
}
if (frameTimeNanos < mLastFrameTimeNanos) {
//信号时间已过,不能再绘制了,等待下一个vsync信号,保证后续时间同步上
scheduleVsyncLocked();
return;
}
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}
try {
//执行相关的callback任务
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
} finally {
AnimationUtils.unlockAnimationClock();
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
所以,通过获取到屏幕的每一次刷新信号就可以根据当前时间计算渲染的内容并进行设置,这就是属性动画实现的基本原理。
1.2 Interpolator
时间插值器定义动画的变化率,将控制动画速度的过程独立出来,通过输入的动画执行分数获取到变换后的效果值
在 Android 动画框架中,定义的时间插值器是一个名为 TimeInterpolator 的接口,实现该接口的类需要实现的方法为 float getInterpolation(float input); 其中,input 表示动画执行分数,输入值范围为 [0,1.0];返回的插值表示变换后的值,可以超过 [0,1.0] 这个范围,因此在使用的过程中,需要考虑到兼容超过范围[0,1.0]的情况。
动画执行的时候,根据 input(通常是当前动画执行分数) ,然后通过定义的插值器进行变换(线性计算、非线性计算)得到对应的插值,并将计算的值应用在动画上,实现插值与动画解耦。
自定义插值器只需要完成如下两步即可:
- 新建一个类并实现 Interpolator 接口或者当 minSdkVersion >= 22 的时候,继承自 BaseInterpolator
- 实现 getInterpolation 方法实现自定义插值器的变换逻辑
下面详细介绍插值器在属性动画中的使用。
动画的原理可以概括为在动画执行期间,每一次绘制屏幕的时候计算并绘制动画的变换值,在肉眼感知下,就看见了一组连续的动画(关于动画具体的原理分析,详见第二章 Android 动画详解)。利用系统提供的 Chreographer 类,可以容易的实现在系统每一次重绘屏幕的时候得到通知,开始执行动画操作。在属性动画中,每次屏幕刷新信号到来的时候调用 ValueAnimator#doAnimationFrame(long frameTime) 方法,处理动画当前帧并在需要的时候(开始时间初始化,动画从暂停状态恢复运行等)设置动画的开始时间,并最终会调到 ValueAnimator#animateBasedOnTime(long currentTime) 方法。
// ValueAnimator.java
/**
* 根据时间处理给定的动画帧
*
* @param currentTime 当前帧开始绘制时间戳,根据 Choreographer 获取当前帧开始绘制时间戳(ms)
* @return true:动画执行完成;false:动画未执行完
*/
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
// 计算动画的执行时长,该方法获取的为设置的动画时长,默认时长为 300ms
final long scaledDuration = getScaledDuration();
// 计算动画执行分数,根据总执行时间除以动画时长,计算出当前为动画当前执行的次数
// 若动画时长大于0,则计算结果为: (当前时间戳-动画开始执行时间戳)/动画时长
// 若动画时长小于等于0,则执行分数为 1
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
// 上次执行分数,即上一帧动画时执行的总次数
final float lastFraction = mOverallFraction;
// 是否是新的一轮动画,若当前动画执行分数>上次动画执行分数,转换为int后比较即表示为新一轮动画
final boolean newIteration = (int) fraction > (int) lastFraction;
// 动画是否结束
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
(mRepeatCount != INFINITE);
// 时长为0,动画执行完成
if (scaledDuration == 0) {
// 0 duration animator, ignore the repeat count and skip to the end
done = true;
} else if (newIteration && !lastIterationFinished) { // 新一轮动画且没有执行完成,则表示为动画重复执行
// Time to repeat
// 回调动画重复执行的方法
if (mListeners != null) {
int numListeners = mListeners.size();
for (int i = 0; i < numListeners; ++i) {
mListeners.get(i).onAnimationRepeat(this);
}
}
} else if (lastIterationFinished) { // 动画执行完成
done = true;
}
// 动画总体执行分数,用于下一帧到来时进行计算比较,将当前的动画分数进行值修正
mOverallFraction = clampFraction(fraction);
// 本次动画执行的分数,对于总执行分数取小数处理,即每次执行分数范围只能是 [0,1.0]
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
// 根据本次动画执行的分数,进行最终的执行动画操作
animateValue(currentIterationFraction);
}
// 返回当前动画是否执行完成
return done;
}
/**
* 计算动画值并赋值
* 在 ValueAnimator 中此方法计算出当前帧动画的值并回调,由调用方自己实现动画的操作
* 在子类 ObjectAnimator 中重写了此方法,在计算当前帧动画的值后,调用 PropertyValuesHolder#setAnimatedValue 进行赋值实现动画变换
*
* @param fraction 本次动画执行分数
*/
void animateValue(float fraction) {
// 插值器通过本次动画的执行分数计算出最终的执行分数
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// 计算对应的值
// 更过关于根据执行分数计算出最终的值,详见2.3节
mValues[i].calculateValue(fraction);
}
// 回调
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
属性动画和插值器的结合过程即:通过时间戳计算出动画在当前帧下本次动画的执行分数,将此分数通过插值器进行变换,得到最终的动画执行分数,并根据此执行分数计算出动画最终的值(更过关于根据执行分数计算出最终的值,详见2.3节)。
屏幕刷新信号的时间间隔是固定的,可以认为每一帧根据刷新时间戳计算出的当前动画执行分数是线性的,若每次插值器直接返回输入的执行分数,则输出的执行分数也是线性的。当需要不同的动画效果的时候,就根据当前输入的动画执行分数进行变化,常见的插值器实例(输入动画执行分数为 input,输入为 result):
- LinearInterpolator:result = input,输入参数为线性,输出结果也为线性,线性插值器实质上是一个一元一次方程,斜率为1
- AccelerateInterpolator: result=input*input,加速插值器实质上是一个一元二次方程,斜率在input>0时不断增加,实现加速的效果
- CycleInterpolator: result=Math.sin(K*input),K 为一个常数,循环插值器实质上是一个正弦函数,周期循环,实现循环插值的效果
插值器的核心原理即输入一个参数 input(当前动画执行分数),通过对输入参数进行不同的变换,实现不同的输出效果。而结合到动画中则根据动画时间计算出这个输入参数 input,并最终采用插值器输出值进行动画值计算。
二、Android 动画详解
2.1 View Animation
在 View 绘制的时候,计算动画在当前时刻的变化,并将变换应用到 View 的绘制中。这就是 View 动画的基本原理。
动画 | 属性 | 实现概述 |
---|---|---|
AlphaAnimation | 透明度 | setAlpha |
AnimationSet | 承载如上四种基本类型动画 | - |
RotateAnimation | 旋转 | Matrix#setRotate |
ScalAnimation | 缩放 | Matrix#setScale |
TranslateAnimation | 位移 | Matrix#setTranslate |
如上,View 动画支持四种基本类型:View 动画支持 Java 代码或者从 XML 文件创建。
// View.java
public class View {
// 父布局调用进行绘制
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
Transformation transformToApply = null;
// 获取当前 View 设置的动画
final Animation a = getAnimation();
if (a != null) {
// 进行动画初始化及计算
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
// 标记是否改变 Matrix
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
// 获取前面计算设置的 Transformation,用于后续绘制
transformToApply = parent.getChildTransformation();
...
if (transformToApply != null
|| alpha < 1
|| !hasIdentityMatrix()
|| (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
...
if (transformToApply != null) {
if (concatMatrix) {
// 通过 Matrix 执行 位移、缩放、旋转动画
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
// Undo the scroll translation, apply the transformation matrix,
// then redo the scroll translate to get the correct result.
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
// 执行透明度变换动画
float transformAlpha = transformToApply.getAlpha();
if (transformAlpha < 1) {
alpha *= transformAlpha;
parent.mGroupFlags |= ViewGroup.FLAG_CLEAR_TRANSFORMATION;
}
}
}
}
}
}
动画根据当前执行时间计算需要设置的状态值:
public abstract class Animation implements Cloneable {
// 根据当前时间计算 Transformation
// @return True if the animation is still running
public boolean getTransformation(long currentTime, Transformation outTransformation) {
...
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
...
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
applyTransformation(interpolatedTime, outTransformation);
}
...
}
// 计算 getTransformation,子类实现这个方法在给定插值的情况下计算出对应的变换
// 插值最终的实现和动画效果计算方法
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
}
// 透明度
public class AlphaAnimation extends Animation {
// 改变 Transformation 的透明度
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
}
// 旋转
public class RotateAnimation extends Animation {
// 通过 Transformation#mMatrix 实现旋转
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
float scale = getScaleFactor();
if (mPivotX == 0.0f && mPivotY == 0.0f) {
t.getMatrix().setRotate(degrees);
} else {
t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
}
}
}
// 缩放
public class ScaleAnimation extends Animation {
// 通过 Transformation#mMatrix 实现缩放
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float sx = 1.0f;
float sy = 1.0f;
float scale = getScaleFactor();
if (mFromX != 1.0f || mToX != 1.0f) {
sx = mFromX + ((mToX - mFromX) * interpolatedTime);
}
if (mFromY != 1.0f || mToY != 1.0f) {
sy = mFromY + ((mToY - mFromY) * interpolatedTime);
}
if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
}
// 位移
public class TranslateAnimation extends Animation {
// 通过 Transformation#mMatrix 实现位移
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
t.getMatrix().setTranslate(dx, dy);
}
}
小结
- 优点:
- 支持 Java 代码和 XML 创建动画,方便
- 缺点:
- 支持样式少,仅支持4钟
- 仅支持对 View 对象设置动画
2.2 Animate Drawable Graphics
通过屏幕刷新依次绘制下一帧,实现逐帧动画的效果。
支持的逐帧动画:
- AnimationDrawable (added in API level 1): 依次显示静态图片
- AnimatedVectorDrawable (added in API level 21): 依次显示 vector 文件
小结
- 优点
- 实现简单
- 缺点
- 因为是逐帧加载图片资源,因此如果复杂(每一帧图片较大)或帧数较多的动画容易造成卡顿或者OOM
2.3 Property Animation
从前面 Choreographer 知道如果需要利用屏幕刷新回调,那么当前线程一定需要有消息队列
首先我们通过 ValueAnimator 或者 ObjectAnimator(继承自 ValueAnimator) 针对目标构造一个动画,然后开始开始执行动画:
- ValueAnimator 有如下两个开始执行动画的方法,所有的执行逻辑都在一个参数的方法里
-
public void start() :默认调用 start(false) 方法
-
private void start(boolean playBackwards) :playBackwards表示反向播放,用于反向播放动画 reverse() 方法时调用
-
- 在 start(boolean) 方法里:
// ValueAnimator.java
private void start(boolean playBackwards) {
//属性动画是采用订阅 Choreographer 屏幕刷新回调实现的,由前面介绍,Choreographer 的实现是需要消息队列实现兼容没有 VSYNC 信号的设备进行延时任务通知刷新。
// 动画应运行在主线程
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
// 进行一些值的初始化
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
// Calculate the fraction of the current iteration.
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
// 添加屏幕刷新信号回调
// Choreographer 通知屏幕刷新后,通过回调进行计算绘制,完成动画
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
// 若动画还没开始执行或者是逆序播放,则重新初始化动画并通知回调动画开始执行
startAnimation();
// 进行值的修正
if (mSeekFraction == -1) {
// No seek, start at play time 0. Note that the reason we are not using fraction 0
// is because for animations with 0 duration, we want to be consistent with pre-N
// behavior: skip to the final value immediately.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
3.添加屏幕刷新信号回调,从调用线程获取到 AnimationHandler 的单例,并添加屏幕刷新绘制回调
public class AnimationHandler {
// 当前线程的订阅代理管理
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
// 通知动画进行绘制
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
// 还有未完成的动画(还没执行的延时动画),需要继续下一个刷新信号
getProvider().postFrameCallback(this);
}
}
};
// ValueAnimator 调用的屏幕刷新回调
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
// 只对 Choreographer 暴露线程级别的刷新回调代理类
getProvider().postFrameCallback(mFrameCallback);
}
// 通过 ArrayList 管理所有回调
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
// 延时动画
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
// 获取帧管理器
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
// 属性动画的帧管理器实现类
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
// 通过 Choreographer 实现动画的刷新
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
...
}
}
4.通过上述流程,我们就成功的为当前动画添加了屏幕刷新回调。当 Choreographer 接受到屏幕刷新信号后,通知动画进行绘制。实现方法是在 ValueAnimator#doAnimationFrame ,通过层层调用,最后实现的方法如下:
// ValueAnimator.java
void animateValue(float fraction) {
// 插值器利用当前动画执行分数计算出来最终执行分数
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// 分别对设置的属性进行变换计算
mValues[i].calculateValue(fraction);
}
// 动画调用者可以通过 onAnimationUpdate 回调进行更多自定义的设置,比如对一组 View 进行操作
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
// ObjectAnimator.java
void animateValue(float fraction) {
// 获取动画执行的对象,没有指定对象则不执行
final Object target = getTarget();
if (mTarget != null && target == null) {
// We lost the target reference, cancel and clean up. Note: we allow null target if the
/// target has never been set.
cancel();
return;
}
// 调用 ValueAnimator#animateValue 方法,计算属性对应的值
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// 动画设置,比如最终的 alpha 效果通过这儿设置生效
mValues[i].setAnimatedValue(target);
}
}
其中,通过当前动画执行分数计算到最终的动画值走到了 PropertyValuesHolder#calculateValue 方法:
// PropertyValuesHolder.java
void calculateValue(float fraction) {
// 通过构造动画的时候传入的关键帧计算动画值
// 例如根据 ValueAnimator.ofFloat(0,1) 则生成了两个关键帧 0f,1f
// 最后都是通过各种 KeyframeSet 进行计算管理
Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
// IntKeyframeSet.java
// 其他 KeyframeSet 计算方法类似,此处用 IntKeyframeSet 作为示例
// 通过构造动画时设置的值计算出动画的值
class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes {
public Object getValue(float fraction) {
return getIntValue(fraction);
}
public int getIntValue(float fraction) {
//... 进行 fraction 超过边界条件的计算,fraction <= 0,fraction >= 1
if (fraction <= 0f) {
...
} else if(fraction >= 1f) {
...
}
// 从第一组关键帧开始,作为前一个关键帧
IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {
IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
// 若执行分数在当前范围内,则表示匹配成功,进行计算
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
// 计算总体执行分数
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
// Apply interpolator on the proportional duration.
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
// 根据执行分数,在两个动画关键帧之间计算出最终值
return mEvaluator == null ?
prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
intValue();
}
prevKeyframe = nextKeyframe;
}
// shouldn't get here
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
}
}
前面计算出动画对应的属性值后,通过 PropertyValuesHolder#setAnimatedValue 方法,针对设置的属性进行设置:
// PropertyValuesHolder.java
// PropertyValuesHolder 包含具体的属性信息以及设置的动画值
void setAnimatedValue(Object target) {
// 若已经指定 abstract class Property<T, V>,通过set赋值
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
// 反射设置值
// String methodName = getMethodName("set", mPropertyName);
// 最终方法名生成规则为 [set]+[X](mPropertyName首字母大写)+[XXXX](mPropertyName除首字母外的其他内容)
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
小结
- 优点:
- 支持范围广,可以为任何对象(视图和非视图)的任何属性设置动画
- 对象本身实际上被修改,进行位移后的对象点击事件也同步转移
- 缺点:
- 使用比 View 动画稍麻烦