上一篇文章我们记录了view补间动画的源码流程,明白了view动画大概流程就是view.startAnimation(Animation),然后由view发起重绘请求invalidate(),view去调用ViewParent.invalidateChild()跳转到ViewGroup类中,do{ViewParent.invalidateChildInParent}while(mParent!=null),层层遍历到view树体系的DecorView根布局父类ViewRootImpl中,由ViewRootImpl.invalidateChildInparent去发起scheduleTraversal,而scheduleTraversal会将performTraversal打包封装成一个Running丢给Choreographer待执行队列,当surfaceFlinger每隔16.6ms发一个场屏信号时,Running执行,开始view测绘的三大步,measure、layout、draw,view补间动画就是在ViewGroup的dispatchDraw时调用drawChild(三个参数),在这个时候Animation.applyLegacyAnimation被调用,view动画就是在这个方法中真正得到执行的,现在我们来看看属性动画是怎么把真实值给改变的,如何区别view补间动画,首先我们来看看属性动画有哪几种?
很容易,去代码里搜搜哪些类继承了Animator,ValueAnimator继承了Animator,而ObjectAnimator又继承了ValueAnimator,老规矩,我们先回忆一下属性动画的使用,来解释一番
// 步骤1:设置动画属性的初始值 & 结束值
ValueAnimator anim = ValueAnimator.ofInt(20, 600);
// ofInt()作用有两个
// 1. 创建动画实例
// 2. 将传入的多个Int参数进行平滑过渡:此处传入0和1,表示将值从0平滑过渡到1
// 如果传入了3个Int参数 a,b,c ,则是先从a平滑过渡到b,再从b平滑过渡到C,以此类推
// ValueAnimator.ofInt()内置了整型估值器,直接采用默认的.不需要设置,即默认设置了如何从初始值 过渡到 结束值
// 关于自定义插值器我将在下节进行讲解
// 下面看看ofInt()的源码分析 ->>关注1
// 步骤2:设置动画的播放各种属性
anim.setDuration(500);
// 设置动画运行的时长
anim.setStartDelay(500);
// 设置动画延迟播放时间
anim.setRepeatCount(0);
// 设置动画重复播放次数 = 重放次数+1
// 动画播放次数 = infinite时,动画无限重复 anim.setRepeatMode(ValueAnimator.RESTART);
// 设置重复播放动画模式
// ValueAnimator.RESTART(默认):正序重放
// ValueAnimator.REVERSE:倒序回放
// 步骤3:将改变的值手动赋值给对象的属性值:通过动画的更新监听器
// 设置 值的更新监听器
// 即:值每次改变、变化一次,该方法就会被调用一次
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
// 获得改变后的值
System.out.println(currentValue);
// 输出改变后的值
// 步骤4:将改变后的值赋给对象的属性值,下面会详细说明
btn_click.getLayoutParams().width = currentValue;
// 步骤5:刷新视图,即重新绘制,从而实现动画效果
btn_click.requestLayout();
}
});
anim.start();// 启动动画
}
//透明度动画,值范围为0-1,0表示完全透明,1表示完全不透明
animator = ObjectAnimator.ofFloat(textView, "alpha", 1, 0, 1);
animator.setDuration(5 * 1000);
break;
case R.id.rotationBtn:
//旋转动画,第一个数为初始状态,值可正可负
animator = ObjectAnimator.ofFloat(textView, "rotation", 0f, 360f);
animator.setDuration(5 * 1000);
break;
case R.id.translationBtn:
//获取当前对象在屏幕中的X坐标
float curTranslationx = textView.getTranslationX();
//X轴方向平移动画,500f表示在curTranslation位置向右平移半屏,因为上下距离都默认为1000,-500f表示移动到curTranlation位置的左半屏幕位置,最后移回原位。
animator = ObjectAnimator.ofFloat(textView, "translationX", curTranslationx, 500f, -500f,curTranslationx);
animator.setDuration(5 * 1000);
break;
case R.id.scale:
//比例动画,这里把对象的比例扩大或者缩小的动画
animator = ObjectAnimator.ofFloat(textView, "scaleY", 1f, 5f, 4f, 3f, 1f);
animator.setDuration(5 * 1000);
break;
}
animator.start();
因为ObjectAnimator是继承ValueAnimator的,so我们直接看ValueAnimator类型的动画,就能搞清楚属性动画的实现原理,从start开始一步步跟踪代码,我们翻源码的目的是什么?
OK,首先来到ValueAnimator.start(),函数会调用start(boolean flag),flag这个参数源码说的很明白,设置动画是不是reverse_playing,
初始化的那些code先放过不管,start函数中有四行代码,而且我已经发现了startAnimation(),按照追踪view动画的经验,先看这个方法是什么,
追踪startAnimation()函数,发现里边还是在初始化一些数据,最多就做了一步回调,通知动画开始了,,刚刚我们是根据函数名称想当然的直接去找这个函数了,既然没有我们想要的属性动画执行,那就回头继续从头看看,
看addAnimationCallback函数,AnimationHandler.addAnimationFrameCallback,跳到AnimationHandler类中寻找函数
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));
}
}
delay 是延迟执行动画的时间,这个无关大局,先放过不管,callback是ValueAnimator自己(this),代表我们设置的属性动画,mAnimationCallbacks是泛型为AnimationFrameCallback类型的ArrayList集合,这里把实参添加进这个集合中了,而按照代码逻辑,当我们首次执行属性动画的时候(这里的首次执行,说的应该是项目中所有的属性动画的第一次,因为不论我们在哪里使用属性动画,上述流程都会通过动画的start()函数走到AnimationHandler这个类中,都会被add进mAnimationCallbacks的集合中统一管理), if (mAnimationCallbacks.size() == 0) 这个分支会走进去,postFrameCallback是一个回调,那么我们找找在哪里实现了这个接口,
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
/**
* Default provider of timing pulse that uses Choreographer for frame callbacks.
*/
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
//大家应该对Choreographer 这个类很熟悉了,接收surfaceFlinger发送的16.6ms间隔的场屏信息
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);
}
}
根据view动画的经验,程序追踪到Choreographer,我们就能弄懂大部分逻辑了,因为Choreographer是控制屏幕刷新的根本,趁热打铁 mChoreographer.postFrameCallback(callback);
public void postFrameCallback(FrameCallback callback) {
// 1
postFrameCallbackDelayed(callback, 0);
}
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException("callback must not be null");
}
// 2
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
程序走到postCallbackDelayedInternal这个方法,就清晰了(主要是我们要明白Choreographer类的作用,它内部有几个队列,上面方法的第一个参数 CALLBACK_ANIMATION 就是用于区分这些队列的,而每个队列里可以存放 FrameCallback 对象,也可以存放 Runnable 对象,Animation动画就是通过ViewRootImpl封装一个doTraversal的Running,而Animator动画是封装一个FrameCallback进去),这段代码会走到 scheduleFrameLocked(now);这一句,我们来整理一下现在得到的属性动画逻辑(flag=scheduleFrameLocked)
到这里为止,能够肯定,当动画第一次调用 start(),这里的第一次应该是指项目里所有的属性动画里某个动画第一次调用 start(),因为 AnimationHandler 是一个单例的类,它是为所有的属性动画服务的。如果是第一次调用了 start(),那么就会去向底层注册监听下一个屏幕刷新信号的事件。所以动画的处理逻辑应该就是在接收到屏幕刷新信号之后回调到的 mFrameCallback 工作里会去间接的调用到的了。那么我们继续跟踪代码,看看接下来是什么逻辑
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
//这里就是动画真正执行的地方
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
//向底层surfaceFlinger再次注册一个场屏信号(屏幕刷新信号)
getProvider().postFrameCallback(this);
}
}
};
我们先看第二个,因为刚刚我们一直在看向下层注册刷新信号的逻辑,我们知道动画是一个持续的过程,它不能瞬间就完了,否则失去了动画的意思,也就是说每一帧都应该处理一个动画进度,知道动画结束。所以我们要在动画结束之前的每一帧都收到场屏信号(屏幕刷新信号),所以需要在每一帧都去注册surfaceFlinger的下一个监听事件,所以我们能看到getProvider().postFrameCallback(this);又将自己给传递了过去,接着上面那个流程(做个标记,flag=scheduleFrameLocked),调用ValueAnimator.start(),由于mAnimationCallbacks 的size为0所以调用getProvider().postFrameCallback(mFrameCallback ),注册下一个16.6ms的下层surfaceFlinger场屏信号,而当surfaceFlinger回调onVsync()后,mFrameCallback 的接口方法doFrame()会被回调,该方法内部做了两件事,一是去处理当前帧的动画,二则是根据列表的大小是否不为 0 来决定继续向底层注册监听下一个屏幕刷新信号事件,如此反复,直至列表大小为 0,很显然,当一个属性动画完成后它必须要被移除mAnimationCallbacks的列表,否则会一直监听这个屏幕刷新的信号,下面跟着 doAnimationFrame() 来看看,属性动画是怎么执行的:
一是去循环遍历列表,取出每一个 ValueAnimator,然后判断动画是否有设置了延迟开始,或者说动画是否到时间该执行了,如果到时间执行了,那么就会去调用 ValueAnimator 的 doAnimationFrame();
二是调用了 cleanUpList() 方法,看命名就可以猜测是去清理列表,那么应该也就是处理掉已经结束的动画,因为 AnimationHandler 是为所有属性动画服务的,同一时刻也许有多个动画正在进行中,那么动画的结束肯定有先后,已经结束的动画肯定要从列表中移除,这样等所有动画都结束了,列表大小变成 0 了,mFrameCallback 才可以停止向底层注册监听下一个屏幕刷新信号事件,AnimationHandler 才可以进入空闲状态,不用再每一帧都去处理动画的工作。
再去看 ValueAnimator 的 doAnimationFrame(),
public final boolean doAnimationFrame(long frameTime) {
if (mStartTime < 0) {
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
}
// Handle pause/resume
if (mPaused) {
mPauseTime = frameTime;
removeAnimationCallback();
return false;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
// Offset by the duration that the animation was paused
mStartTime += (frameTime - mPauseTime);
}
}
if (!mRunning) {
// If not running, that means the animation is in the start delay phase of a forward
// running animation. In the case of reversing, we want to run start delay in the end.
if (mStartTime > frameTime && mSeekFraction == -1) {
// This is when no seek fraction is set during start delay. If developers change the
// seek fraction during the delay, animation will start from the seeked position
// right away.
return false;
} else {
// If mRunning is not set by now, that means non-zero start delay,
// no seeking, not reversing. At this point, start delay has passed.
mRunning = true;
startAnimation();
}
}
if (mLastFrameTime < 0) {
if (mSeekFraction >= 0) {
long seekTime = (long) (getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
mLastFrameTime = frameTime;
// The frame time might be before the start time during the first frame of
// an animation. The "current time" must always be on or after the start
// time to avoid animating frames at negative time intervals. In practice, this
// is very rare and only happens when seeking backwards.
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);//动画的当前时刻计算都在这里
if (finished) {
endAnimation();
}
return finished;
}
概括一下,这个方法内部其实就做了三件事:
一是处理第一帧动画的一些工作;
二是根据当前时间计算当前帧的动画进度,所以动画的核心应该就是在 animateBaseOnTime() 这个方法里,意义就类似 Animation 动画的 getTransformation()方法;
三是判断动画是否已经结束了,结束了就去调用 endAnimation(),按照我们之前的猜测,这个方法内应该就是将当前动画从 mAniamtionCallbacks 列表里移除。我们先来看动画结束之后的处理工作
private void endAnimation() {
if (mAnimationEndRequested) {
return;
}
removeAnimationCallback();//remove 在这里
mAnimationEndRequested = true;
mPaused = false;
boolean notify = (mStarted || mRunning) && mListeners != null;
if (notify && !mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
notifyStartListeners();
}
mRunning = false;
mStarted = false;
mStartListenersCalled = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
if (notify && mListeners != null) {
ArrayList tmpListeners =
(ArrayList) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationEnd(this, mReversing);
}
}
// mReversing needs to be reset *after* notifying the listeners for the end callbacks.
mReversing = false;
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
System.identityHashCode(this));
}
}
private void removeAnimationCallback() {
if (!mSelfPulse) {
return;
}
getAnimationHandler().removeCallback(this);
}
public void removeCallback(AnimationFrameCallback callback) {
mCommitCallbacks.remove(callback);
mDelayedCallbackStartTime.remove(callback);
int id = mAnimationCallbacks.indexOf(callback);
if (id >= 0) {
mAnimationCallbacks.set(id, null);
mListDirty = true;
}
}
mAnimationCallbacks.set(id, null);正解,就是将其置null,然后判断是null 就remove,
上面就是处理动画第一帧的工作问题
再看 animateBasedOnTime(currentTime);//动画的当前时刻计算都在这里
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, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
从这里开始,就是在计算当前帧的动画逻辑了,整个过程跟 Animation 动画基本上差不多。上面的代码里,我省略了一部分,那部分是用于根据是否设置的 mRepeatCount 来处理动画结束后是否需要重新开始,这些我们就不看了,我们着重梳理一个正常的流程下来即可。
所以,概括一下,这个方法里其实也就是做了三件事:
一是,根据当前时间以及动画第一帧时间还有动画持续的时长来计算当前的动画进度。
二是,确保这个动画进度的取值在 0-1 之间,这里调用了两个方法来辅助计算,我们就不跟进去了,之所以有这么多的辅助计算,那是因为,属性动画支持 setRepeatCount() 来设置动画的循环次数,而从始至终的动画第一帧的时间都是 mStrtTime 一个值,所以在第一个步骤中根据当前时间计算动画进度时会发现进度值是可能会超过 1 的,比如 1.5, 2.5, 3.5 等等,所以第二个步骤的辅助计算,就是将这些值等价换算到 0-1 之间。
三就是最重要的了,当前帧的动画进度计算完毕之后,就是需要应用到动画效果上面了,所以 animateValue() 方法的意义就是类似于 Animation 动画中的 applyTransformation()。
我们都说,属性动画是通过修改属性值来达到动画效果的,那么我们就跟着 animateValue() 进去看看:
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);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
这里干的活我也大概的给划分成了三件事:
一是,根据插值器来计算当前的真正的动画进度,插值器算是动画里比较重要的一个概念了,可能平时用的少,如果我们没有明确指定使用哪个插值器,那么系统通常会有一个默认的插值器。
二是,根据插值器计算得到的实际动画进度值,来映射到我们需要的数值。这么说吧,就算经过了插值器计算之后,动画进度值也只是 0-1 区间内的某个值而已。而我们通常需要的并不是 0-1 的数值,比如我们希望一个 0-500 的变化,那么我们就需要自己在拿到 0-1 区间的进度值后来进行转换。第二个步骤,大体上的工作就是帮助我们处理这个工作,我们只需要告诉 ValueAnimator 我们需要 0-500 的变化,那么它在拿到进度值后会进行转换。
三就只是通知动画的进度回调而已了。
这个时候 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentValue = (Integer) animation.getAnimatedValue();
// 获得改变后的值
System.out.println(currentValue);
// 输出改变后的值
// 步骤4:将改变后的值赋给对象的属性值,下面会详细说明
btn_click.getLayoutParams().width = currentValue;
// 步骤5:刷新视图,即重新绘制,从而实现动画效果
btn_click.requestLayout();
}
});就能收到当前帧的数据了