最近由于项目需要,写了一个动画。历经坎坷,花了好几天的时间,被 UX 批评了好几次,事后回想,发现这个动画其实也比较简单,无非就是对一系列动作的拆解工作,形成一系列的 animator , 然后按照时间顺序执行就可以了,动画效果图如下:
虽然写的这个动画比较简单,但是在写动画过程中了解了一些 Animator 和 Animation 的原理,在此记录一下,毕竟温故知新,可以为师矣。本篇涉及到的Android 源码 7.0 进行分析。
Animator
以 ValueAnimator 为例,从 start() 方法开始分析,代码如下:
private void start(boolean playBackwards) {
.....
mReversing = playBackwards;
...
// 变量重置初始化
mStarted = true;
mPaused = false;
mRunning = 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 = 0;
// 重点关注此处 AnimationHandler
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
if (mStartDelay == 0 || mSeekFraction >= 0) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
// 看看该方法具体做了什么.
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);
}
}
}
在 startAnimation() 中,做了一些变量的初始化工作,重点关注该方法中的 AnimationHandler 和 startAnimation() 具体做了什么,首先看startAnimation() 代码如下:
/**
* Called internally to start an animation by adding it to the active animations list. Must be
* called on the UI thread.
*/
private void startAnimation() {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
}
mAnimationEndRequested = false;
initAnimation();
mRunning = true;
if (mSeekFraction >= 0) {
mOverallFraction = mSeekFraction;
} else {
mOverallFraction = 0f;
}
if (mListeners != null) {
notifyStartListeners();
}
}
可以看到,在startAnimation() 中,仅仅只是进行了动画的初始化工作,并且通知 listener 去回调 onAnimationStart() 方法,那么真正的动画执行时机是什么时候呢,回到刚才的 AnimationHandler ,其addAnimationFrameCallback() 代码如下:
/**
* Register to get a callback on the next frame after the delay.
*/
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
上述方法比较简单,首先看下getProvider()实例如下代码:
/**
* Default provider of timing pulse that uses Choreographer for frame callbacks.
*/
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
@Override
public void postCommitCallback(Runnable runnable) {
mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
}
@Override
public long getFrameTime() {
return mChoreographer.getFrameTime();
}
@Override
public long getFrameDelay() {
return Choreographer.getFrameDelay();
}
@Override
public void setFrameDelay(long delay) {
Choreographer.setFrameDelay(delay);
}
}
在上述类中,有个成员变量为 Choreographer(编舞者) 实例,它负责提供垂直同步信号(每次间隔16.67ms),用来进行屏幕刷新,如果对 Choreographer 有兴趣可以参考Android Choreographer 源码分析这篇文章。postFrameCallback() 方法向 Choreographer 注册了一份 callback , 在 Choreographer 接收到垂直同步信号后,便会对该 callback 实例进行回调。下来看看这个 callback 是什么,代码如下:
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
callback最终会回调到 doFrame() 方法,在该方法中,通过 doAnimationFrame() 真正执行更新逻辑,如果动画没有结束,那么将该callBack 重新进行注册,接收后续的垂直同步信息。doAnimationFrame() 代码如下:
/**
* Processes a frame of the animation, adjusting the start time if needed.
*
* @param frameTime The frame time.
* @return true if the animation has ended.
* @hide
*/
public final void doAnimationFrame(long frameTime) {
AnimationHandler handler = AnimationHandler.getInstance();
....
//
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
}
可以看到,动画的主要逻辑都在 animateBasedOnTime() 里面, 并且根据返回值来判断是否要进行结束的回调,下面看一下该方法具体做了什么,代码如下:
/**
* This internal function processes a single animation frame for a given animation. The
* currentTime parameter is the timing pulse sent by the handler, used to calculate the
* elapsed duration, and therefore
* the elapsed fraction, of the animation. The return value indicates whether the animation
* should be ended (which happens when the elapsed time of the animation exceeds the
* animation's duration, including the repeatCount).
*
* @param currentTime The current time, as tracked by the static timing handler
* @return true if the animation's duration, including any repetitions due to
* <code>repeatCount</code> has been exceeded and the animation should be ended.
*/
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
final long scaledDuration = getScaledDuration();
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;
final boolean newIteration = (int) fraction > (int) lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
(mRepeatCount != INFINITE);
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);
float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
animateValue(currentIterationFraction);
}
return done;
}
方法的前半部分主要进行当前fraction进度的修正,以及是否要执行onAnimationRepeat() 的回调,真正刷新继续往下找,animateValue()方法如下:
void animateValue(float fraction) {
// 根据插值器类型的不同,计算当前fraction 对应的值..
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// 将 fraction 转换成真正的 animatedValue ..
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
// 最熟悉的 onAnimationUpdate() 回调,可以在该回调做
// 任何自己想要做的操作, 根据上面算出的 animatedValue
// 去更新自己想要的属性。
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
从代码可以看出,首先通过插值器去修正 fraction 的值,插值器类型可以通过 setInterpolator() 来指定,得到 fraction 以后,再将当前 fraction 转换成真正的 animatedValue ,最后通过 listener完成onAnimationUpdate() 回调即可。
自此, Animator 大体运作原理分析完毕,Animator 的核心逻辑还是依靠 Choreographer 来完成更新,并且需要注意一点的是, 因为 AnimationHandler 和 Choregrapher 是 ThreadLocal 线程私有的,每一个线程如果需要都会有各自的 Choregrapher 实例,所以 ValueAnimator 是完全可以运行在子线程中的,只要在 ValueAnimator 运行中不涉及到 UI 的更新即可。这一点 ValueAnimator 注释也已经很清楚了,说明如下:
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
* to true if called from the reverse() method.
*
* <p>The animation started by calling this method will be run on the thread that called
* this method. This thread should have a Looper on it (a runtime exception will be thrown if
* this is not the case). Also, if the animation will animate
* properties of objects in the view hierarchy, then the calling thread should be the UI
* thread for that view hierarchy.</p>
*
* @param playBackwards Whether the ValueAnimator should start playing in reverse.
*/
依靠着 Choreographer ,在理想情况下,ValueAnimator 每间隔 16.67ms 会重新计算 animatorValue , 以此来更新界面。在实际情况中,可以用 Choreographer 做很多事情,比如应用的帧率检测等等,在此推荐一个优秀的帧率检测开源项目。其核心也是通过 choreographer 来实现。
戳我 >> TinyDancer
参考文章:
View 动画 Animation 运行原理解析
Android Choreographer 源码分析
Android Choreographer 源码分析