android属性动画是开发中经常使用的一项技能,但是我之前却从没有深究过动画实现原理,本文基于android 8.0,撸了一把ObjectAnimator源码,分析属性动画执行关键的过程。
一、动画执行的两个主要的问题
二、带着上述的两个问题,以alpha属性动画为例,分析ObjectAnimator的初始化
基本使用:
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0);
alpha.setDuration(1000);
alpha.start();
1.跟进ofFloat方法:执行了首先new ObjectAnimator()的操作,设置view和属性名
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);
setPropertyName(propertyName);
}
然后执行了一个比较重要的初始化方法:setFloatValues(),这里将初始化帧序列
@Override
public void setFloatValues(float... values) {
if (mValues == null || mValues.length == 0) {
if (mProperty != null) {
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
} else {
//从这个入口继续往下,因为我们传入的是一个属性名,是一个字符串
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}
2.执行PropertyValuesHolder.ofFloat()方法,进入PropertyValuesHolder类中,初始化的是PropertyValuesHolder的子类:FloatPropertyValuesHolder,然后执行了FloatPropertyValuesHolder类中的setFloatValues()方法,最终执行父类PropertyValuesHolder的setFloatValues()方法。
//父类的setFloatValues方法,调用KeyframeSet.ofFloat方法初始化mKeyframes
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}
//子类FloatPropertyValuesHolder的方法,调用了父类的setFloatValues(),并将mKeyframes强转成FloatKeyframes类型对象
@Override
public void setFloatValues(float... values) {
super.setFloatValues(values);
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
3.跟进KeyframeSet.ofFloat(values)方法
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
//初始化一个FloatKeyframe 类型数组
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
//只有一帧时(参数里只有一个value),初始帧默认为0和结束帧设为该value
if (numKeyframes == 1) {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
if (Float.isNaN(values[0])) {
badValue = true;
}
} else {
//初始化第一个帧
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
//这里比较重要:将每一个value值封装成一个FloatKeyframe(一个动画帧),第一个参数是当前value参数的下标与总value参数个数的比值,这个值会在后面用来分配每两个帧之间的动画时间,第二个参数为我们传入的value值
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
if (badValue) {
Log.w("Animator", "Bad value (NaN) in float animator");
}
//将keyframes数组封装成FloatKeyframeSet并返回FloatKeyframeSet对象,即在FloatKeyframeSet保存了帧序列的数组
return new FloatKeyframeSet(keyframes);
}
在FloatKeyframe中只是简单保存了我们传入的参数
FloatKeyframe(float fraction, float value) {
//下标比值
mFraction = fraction;
//在上面的Keyframe.ofFloat()中传入的value值
mValue = value;
mValueType = float.class;
mHasValue = true;
}
4.总结一下ObjectAnimator.ofFloat()这个方法主要干了什么:分别将view和mPropertyName属性值用target变量、mPropertyName变量保存;setFloatValues()方法中初始化一些帧序列,即:我们传入的每一个value值会用FloatKeyframe对象封装成一个序列帧(Keyframe.ofFloat()方法),多个序列帧对象封装成FloatKeyframeSet对象,该对象是封装了多个序列帧的集合,该帧集合对象在PropertyValuesHolder对象的setFloatValues()方法中被返回,并保存在mKeyframes变量里面;
三、start()方法开启动画分析
直接进入ValueAnimator的start()方法中,playBackwards:是否逆序播放,传入的是false,我们跳过函数前面的一些判断初始化部分,看到addAnimationCallback函数
private void start(boolean playBackwards) {
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;
//这里函数将添加回调,即解决文章开始提出的第一个问题:动画每一帧的回调的信号是怎么产生的
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
//开始动画的一些初始化
startAnimation();
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
下面我们主要分析start()方法中的addAnimationCallback()、startAnimation()和setCurrentFraction()方法,在这几个方法中解决了我们最开始的两个问题
1.动画引擎:addAnimationCallback(),动画怎么获得信号然后刷新的,将在这里看见,该函数调用了AnimationHandler类的addAnimationFrameCallback()方法
//FrameCallback:信号首先在这个回调里接收
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
//调用doAnimationFrame()具体执行动画刷新任务,进而调用ValueAnimatord的doAnimationFrame()方法,并传入一个时间戳
doAnimationFrame(getProvider().getFrameTime());
//mAnimationCallbacks保存每一个要接收信号的ValueAnimator实例
if (mAnimationCallbacks.size() > 0) {
//因为AnimationHandler是线程私有且单例,当还有动画在执行(即mAnimationCallbacks不空),就要接收下一次的刷新信号,每次都接收信号就类似一个Timer定时器
getProvider().postFrameCallback(this);
}
}
};
/**
* 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()获得MyFrameCallbackProvider实例
getProvider().postFrameCallback(mFrameCallback);
}
//加入到列表,回调的时候要遍历这个列表
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
/**
* 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) {
//Choreographer 是接收底层VSync信号刷新屏幕信号的一个类,当将callback加入,意味着在下一次刷新信号到来时要回调该callback,
//即形成了信号源,给了动画刷新以时机。在ViewRootImpl有着类似的操作,也是通过post这样一个回调完成整个UI的绘制,理论上是16ms回调一次
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);
}
}
2.刷新机制清楚了,那么是怎么让view动起来的呢?直接进入startAnimation()->initAnimation()方法。
①在ObjectAnimator调用了setupSetterAndGetter()和父类的initAnimation()方法
@Override
void initAnimation() {
if (!mInitialized) {
// mValueType may change due to setter/getter setup; do this before calling super.init(),
// which uses mValueType to set up the default type evaluator.
final Object target = getTarget();
if (target != null) {
final int numValues = mValues.length;
//还记得mValues是什么吗,这是在初始化ObjectAnimator的时候,setFloatValues()方法中设置的,是一个FloatPropertyValuesHolder对象,保存了帧序列集合
for (int i = 0; i < numValues; ++i) {
//target是最初初始化ObjectAnimator的时候传入的View对象
mValues[i].setupSetterAndGetter(target);
}
}
super.initAnimation();
}
}
②我们进入到setupSetterAndGetter()方法,这里mSetter = null,主要调用setupSetter()方法为mSetter 赋值,在setupSetter方法中调用了setupSetterOrGetter()方法,传入的参数是view.class对象,下面看一下这个方法干了什么
void setupSetterAndGetter(Object target) {
//删除mProperty!=null的情况的代码,因为我们传入的是一个属性值
if (mProperty == null) {
Class targetClass = target.getClass();
if (mSetter == null) {
//初始化mSetter,mSetter保存了view的一个Method对象
setupSetter(targetClass);
}
List keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (mGetter == null) {
setupGetter(targetClass);
if (mGetter == null) {
// Already logged the error - just return to avoid NPE
return;
}
}
try {
Object value = convertBack(mGetter.invoke(target));
kf.setValue(value);
kf.setValueWasSetOnStart(true);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
}
}
③setupSetterOrGetter()方法干了什么:
//propertyMapMap是全局的static final的map对象(类型是HashMap>),
//view.class为key,value也是一个hashmap,用来保存属性名和其对应的Method对象
private Method setupSetterOrGetter(Class targetClass,
HashMap> propertyMapMap,
String prefix, Class valueType) {
Method setterOrGetter = null;
synchronized(propertyMapMap) {
//先在全局map中以view.class为key查找
HashMap propertyMap = propertyMapMap.get(targetClass);
boolean wasInMap = false;
//找到了直接返回
if (propertyMap != null) {
wasInMap = propertyMap.containsKey(mPropertyName);
if (wasInMap) {
setterOrGetter = propertyMap.get(mPropertyName);
}
}
//没找到,要新建,进入了getPropertyFunction()方法,传入参数为view.class,prefix为字符串"set",valueType方法参数,float.class
if (!wasInMap) {
setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
if (propertyMap == null) {
propertyMap = new HashMap();
propertyMapMap.put(targetClass, propertyMap);
}
propertyMap.put(mPropertyName, setterOrGetter);
}
}
return setterOrGetter;
}
//这个方法是根据属性名和前缀("set"),以反射的方式拿到View的对应的Method
private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
Method returnVal = null;
//根据属性名和前缀("set")拼接方法名,比如我们传入的属性值是"alpha",则这里返回setAlpha,
//这里就看到了我们初始化ObjectAnimator时传入的第二个参数"alpha"的作用了,就是为了反射拿到该对象的方法
String methodName = getMethodName(prefix, mPropertyName);
Class args[] = null;
if (valueType == null) {
try {
returnVal = targetClass.getMethod(methodName, args);
} catch (NoSuchMethodException e) {
// Swallow the error, log it later
}
} else {
args = new Class[1];
Class typeVariants[];
if (valueType.equals(Float.class)) {
typeVariants = FLOAT_VARIANTS;
} else if (valueType.equals(Integer.class)) {
typeVariants = INTEGER_VARIANTS;
} else if (valueType.equals(Double.class)) {
typeVariants = DOUBLE_VARIANTS;
} else {
typeVariants = new Class[1];
typeVariants[0] = valueType;
}
for (Class typeVariant : typeVariants) {
args[0] = typeVariant;
try {
returnVal = targetClass.getMethod(methodName, args);
if (mConverter == null) {
// change the value type to suit
mValueType = typeVariant;
}
return returnVal;
} catch (NoSuchMethodException e) {
// Swallow the error and keep trying other variants
}
}
// If we got here, then no appropriate function was found
}
if (returnVal == null) {
Log.w("PropertyValuesHolder", "Method " +
getMethodName(prefix, mPropertyName) + "() with type " + valueType +
" not found on target class " + targetClass);
}
return returnVal;
}
④总结一下initAnimation()主要作用:反射拿到动画执行过程中需要调用的view对应的方法,这里是setAlpha。
3.setCurrentFraction()->animateValue(),该方法调用了calculateValue(),该函数我们结合doAnimationFrame()函数说明(该函数是在ValueAnimator中回调的,在刷新机制介绍过),
①doAnimationFrame()函数
//每次刷新都会回调的方法,回调函数中除了一些必要的错误处理和判断,最重要的是animateBasedOnTime()函数
public final boolean doAnimationFrame(long frameTime) {
if (mStartTime < 0) {
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 (mStartTime > frameTime && mSeekFraction == -1) {
return false;
} else {
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;
//防止回调时间比动画开始时间还要早
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
return finished;
}
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);
//这里也调用了该函数,可见这是让View动起来的关键
animateValue(currentIterationFraction);
}
return done;
}
②动起来的关键:animateValue()函数,该函数传入了一个动画执行比率值,描述动画执行进度,父类的该函数又调用了PropertyValuesHolder对象的一个calculateValue()函数,然后调用setAnimatedValue()函数
@CallSuper
@Override
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;
}
super.animateValue(fraction);
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//将计算后的属性值赋值给我们需要动画的view
mValues[i].setAnimatedValue(target);
}
}
//父类的animateValue方法
@CallSuper
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);
}
}
}
//我们看一下calculateValue()是做了什么操作,调用了初始化阶段定义的帧序列集合(mFloatKeyframes是FloatKeyframeSet实例)的getFloatValue方法,最终会调用FloatKeyframeSet的getFloatValue方法
@Override
void calculateValue(float fraction) {
mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}
③getFloatValue(float fraction)方法,参数也是动画执行进度值,这个函数用来根据这个线性的时间进度计算属性值
@Override
public float getFloatValue(float fraction) {
//动画最开始
if (fraction <= 0f) {
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
} else if (fraction >= 1f) {
//最后结束
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
}
//我们主要看一下这里,上面两种情况类似
//拿到第一帧,再次说明,这些帧序列是初始化动画时根据每一个value封装起来的。
FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
//getFraction()拿的是什么:初始化帧序列的时候放的一个参数,该参数描述该帧的下标对总帧数的比值,
//比如一共10帧(传入了10个value),第一帧是0,第二帧为1/9,第三帧为2/9,以此类推,参考帧初始化那里
//循环的作用:遍历拿到这样的两帧:刚好前一帧的getFraction()小于fraction且后一帧的大于fraction
for (int i = 1; i < mNumKeyframes; ++i) {
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
//根据fraction 获取一个相对比值,
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
// Apply interpolator on the proportional duration.
//获取动画插值器返回的值,插值器可以动态改变动画执行进度速率等,这里为null(没看到有初始化这东西的地方,自定义差值器被使用是在ValueAnimator中的)
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
//mEvaluator是可以自定义的,可以实现更丰富的动画,可以自行百度
//从该公式的计算可以看出,每两个value之间的时间片是均匀的(线性插值器情况下),即每两个value之间的变换时间是一样的
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
}
prevKeyframe = nextKeyframe;
}
// shouldn't get here
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}
④最后一步:setAnimatedValue(),将计算的属性赋值给view,还记得我们之前反射拿到的Method方法吗,这个Method就是mSetter,这个方法只是简单的反射执行动画而已,现在知道了view是怎么动起来的了。
如果需要自定义高级动画,例如:在自定义view中定义一个方法为setXxxx(Object object),我们就可以将”alpha”替换成”xxxx”,参数Object也可以自定义,使用ObjectAnimator.ofObject(),其中一个参数为TypeEvaluator 类型,具体可以参考setEvaluator(TypeEvaluator value)这个方法的使用,计算和之前alpha动画类似,在KeyframeSet类的getValue(float fraction)方法中,将在自定义的TypeEvaluator计算之后,反射调用setXxxx方法。
@Override
void setAnimatedValue(Object target) {
if (mFloatProperty != null) {
mFloatProperty.setValue(target, mFloatAnimatedValue);
return;
}
if (mProperty != null) {
mProperty.set(target, mFloatAnimatedValue);
return;
}
if (mJniSetter != 0) {
nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
return;
}
if (mSetter != null) {
try {
//mFloatAnimatedValue 就是前面计算出的属性值
mTmpValueArray[0] = mFloatAnimatedValue;
//反射执行动画,即调用了view.setAlpha(mTmpValueArray)方法,刷新动画,到此,整个动画执行流程完成
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}