1.先看一段动画的代码实现
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0,1);
alpha.setDuration(500);
alpha.start();
代码很简单,上面三行代码就可以开启一个透明度变化的动画。 那么android系统到底是如何实现的呢?进入源码分析。
1)看第一行代码:
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0,1);
创建了一个ObjectAnimator对象,并把values数组设置给了anim对象。
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
ObjectAnimator 构造函数中。将传过来的View对象和propertyName赋值给成员变量。
private ObjectAnimator(Object target, String propertyName) {
//将传过来的View对象赋值给成员变量mTarget
setTarget(target);
//将propertyName赋值给成员变量mPropertyName
setPropertyName(propertyName);
}
注意这个mTarget为什么要用一个软引用?
那是为了防止Activity发生内存泄漏。因为会有Activity已经退出,但是动画可能还未执行完,这个时候View得不到释放的话,会引发Activity内存泄漏。
private WeakReference
再看第二行代码做了啥?anim.setFloatValues(values);
首次进来mValues==null,mProperty==null,所以会执行这行代码。 setValues(PropertyValuesHolder.ofFloat(mPropertyName, values))。
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);
}
}
setValue将得到的 PropertyValuesHolder数组赋值给成员变量 PropertyValuesHolder[] mValues;
再看PropertyValuesHolder.ofFloat(mPropertyName, values));
先调用super构造函数,将propertyName赋值给父类的mPropertyName,
public FloatPropertyValuesHolder(String propertyName, float... values) {
super(propertyName);
setFloatValues(values);
}
然后再调用setFloatValues(values);
public void setFloatValues(float... values) {
super.setFloatValues(values);
//将mKeyframes强转为mFloatKeyframes
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
//调用父类方法创建了KeyframeSet对象,赋值给了mKeyframes
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}
KeyframeSet.ofFloat(values);这行代码创建了一个关键帧的集合。
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
//创建一个value长度的 FloatKeyFrame的数组
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
//numKeyframes==1的话,其实是没有View是没有动画的。如果传过来的values的长度是1的话,会报错的。
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 {
//下面的代码才是关键的 Keyframe ofFloat(float fraction, float value)是创建关键帧。
//fraction英文单词意思是部分,在这作为参数的意思是:从动画启示位置,到当前位置,所占的整个动画的百分比。
//value就是某个部分对应的属性值。
// 比如传进来的value值是1.0f 2.0f 3.0f 4.0f,5.0f。整个动画有5个值。因为1.0是初始值,要完成整个动画需要4步。
//从1-2,2-3,3-4,4-5;4个部分。
//第0个位置是起始位置,所以他所在的部分就是0。第一个位置就是四分之一,第二个就是四分之二....
//第i个位置,所在整个动画的部分就是i/(i-1)。而这个位置对应的动画的属性值,就是value[i]
//所以这个keyframes[]数组的目的就是保存,动画的关键位置所占的百分比和关键位置对应的属性值。
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
return new FloatKeyframeSet(keyframes);
}
到这为止,第一行代码执行完毕。
ObjectAnimator.ofFloat(view, "alpha", 1, 0,1)
将view赋值给ObjectAnimator成员变量。
将propertyName赋值给PropertyValuesHolder,会通过属性name来反射它的set方法,用来修改属性值。
创建KeyframeSet,关键帧集合。将value数组转换成对应的关键帧集合,通过动画执行的时间,来计算当前时间对应的属性值,然后再调用view的set属性方法,从而达到形成动画的目的。
这块的代码会再后面看到。
2).看动画的第二行代码alpha.start();
ObjectAnimator的父类是ValueAnimator。start()里面调用到的方法会在子类和父类里跳来跳去,这也增大了阅读的难度。
首先看ValueAnimator#start(boolean playBackwards)方法
addAnimationCallback:向Choreographer注册回调函数,我们知道Choreographer可以接受Vsync信号,16.66ms一次,也是屏幕刷新一次的时间。这样在屏幕刷新的时候,就可以通过向Choreographer注册回调函数进行动画的更新。
private void start(boolean playBackwards) {
//Animators 必须运行在一个Looper不能为空的线程中,因为动画需要涉及到Choreographer。
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
mStartTime = -1;
//这个是一个回调函数。这块是由Choreographer回调的,稍后分析。
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
//开始动画。
startAnimation();
}
}
先看startAnimation方法(),会在这个方法中调用initAnimation();
在这会先调用子类ObjectAnimator,然后在调用父类的ValueAnimator的initAnimation方法。
先看子类的initAnimation(),这个方法根据propertyName来反射view的set属性方法。
void initAnimation() {
if (!mInitialized) {
//先拿到target,也就是view对象。
final Object target = getTarget();
if (target != null) {
// PropertyValuesHolder[] mValues;这个values就是PropertyValuesHolder的集合。
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//在PropertyValuesHolder中传进了属性值,下面这行代码就是根据属性值,来反射view的set方法,
//通过set方法,就可以动态的改变view的属性值的变化。
mValues[i].setupSetterAndGetter(target);
}
}
//调用父类的initAnimation()方法
super.initAnimation();
}
}
再看父类ValueAnimator的initAnimation方法。调用了PropertyValuesHolder的init()方法。
在init方法中,向KeyframeSet关键帧集合设置了一个估值器,这个用来计算属性值的,后面会看到具体的计算方法。
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//调用PropertyValuesHolder#init方法
mValues[i].init();
}
mInitialized = true;
}
}
void init() {
if (mEvaluator == null) {
//得到一个估值器
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator : null;
}
if (mEvaluator != null) {
//向KeyframeSet中设置一个估值器,这个估值器用来计算动画在某个时刻的属性值。
mKeyframes.setEvaluator(mEvaluator);
}
}
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
public class FloatEvaluator implements TypeEvaluator {
//This function returns the result of linearly interpolating the start and end values
这个方法返回一个在动画开始和结束之间的一个线性的结果。其实就是个一元一次方程,来计算动画当前的位置。
//result = x0 + t * (v1 - v0)
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
至此,initAnimation的代码已经执行完毕。主要做的工作可以总结为两点:
1.调用PropertyValuesHolder的setupSetterAndGetter方法,通过反射拿到View的setter方法。
2.向KeyframeSet中设置一个估值器,用来计算动画某一时刻的属性值。
3)接下来看ValueAnimator#addAnimationCallback
这个方法是向Choreographer设置了一个会回调函数,每隔16.66ms回调一次,用来刷新动画。
还设置了一个回调集合,在Choreographer的回调函数中,回调集合里面的回调函数,来实现属性动画的刷新
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
//getAnimationHandler 就是上面创建的AnimationHandler。
//将this作为 AnimationFrameCallback的回调,会回调doAnimationFrame(long frameTime)
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
//AnimationHandler#addAnimationFrameCallback
getProvider()拿到的是MyFrameCallbackProvider。
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
//向Choreographer加入一个回调函数mFrameCallback
getProvider().postFrameCallback(mFrameCallback);
}
//将添加的回调函数加入一个回调的集合。
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
}
先看这个getProvider().postFrameCallback(mFrameCallback);这个就是向Choreographer注册一个回调。
final Choreographer mChoreographer = Choreographer.getInstance();
//这行代码是向编舞者Choreographer添加了一个回调函数。
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
Choreographer中
public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
下面这行代码就是向Choreographer添加CallBackType为CALLBACK_ANIMATION,Token为FRAME_CALLBACK_TOKEN的回调函数。 callback 就是传进来的mFrameCallback。
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
省略中间的调用过程。。。这块的代码在Choreographer源码分析过。
MyFrameCallbackProvider#postFrameCallback就是向Choreographer添加一个回调函数。 我们知道,Choreographer在接收到Vsync信号后调用这些回调函数。
void doFrame(long frameTimeNanos, int frame) {
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
最终会调到这里,根据上面传过来的token,转换成不同的回调函数,调用不同的方法。
//在将View绘制时,调用的是else分支的回调
//在动画这里,传进来的是mFrameCallback,Choreographer.FrameCallback的实例,会调用到doFrame方法
public void run(long frameTimeNanos) {
if (token == FRAME_CALLBACK_TOKEN) {
((FrameCallback)action).doFrame(frameTimeNanos);
} else {
((Runnable)action).run();
}
}
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
//再次向Choreographer注册回调,等到下一次Vsync信号来的时候调用,
//针对于60Hz的屏幕,刷新时间间隔是16.66ms,也就是Vsync回调的时间间隔
//也就是说属性动画16.66毫秒会改变一次
getProvider().postFrameCallback(this);
}
}
};
Choreographer中每个16.6ms会回调doFrame方法(),在doAnimationFrame方法中,就会回调注册的回调集合。
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
//遍历mAnimationCallbacks,调用callBack回调函数,
//这个回调函数是ValueAnimator的doAnimationFrame
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
}
}
}
doAnimationFrame是AnimationFrameCallback的回调函数,由ValueAnimator实现。
public final boolean doAnimationFrame(long frameTime) {
//frameTime 这个时间是从Choreographer传过来的时间,
//记录为上一次动画刷新的时间
mLastFrameTime = frameTime;
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
return finished;
}
public final boolean doAnimationFrame(long frameTime) {
//frameTime 这个时间是从Choreographer传过来的时间,
//记录为上一次动画刷新的时间
mLastFrameTime = frameTime;
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
return finished;
}
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
//拿到总时间
final long scaledDuration = getScaledDuration();
//通过计算得到动画当前执行占比多少。(currentTime - mStartTime)动画执行的时间
//除以scaledDuration总时间,得到就是已经执行的部分,如果是一个重复的动画,这个值可能会大于1.
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
//下面通过计算对fraction进行修正,减去重复执行的部分,得到真正的在一次动画中要执行到哪一部分
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
注意animateValue,这个方法在父类ValueAnimator和子类ObjectAnimator都有实现。
所以这里先调用子类ObjectAnimator的方法。
//这个方法是调用的子类的方法
void animateValue(float fraction) {
final Object target = getTarget();
if (mTarget != null && target == null) {
cancel();
return;
}
//先调用父类的方法
super.animateValue(fraction);
//再回到子类
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//给View设置改变后的属性值
mValues[i].setAnimatedValue(target);
}
}
先看super.animateValue方法,这个方法就是去计算动画变动后的属性值。
void animateValue(float fraction) {
//通过插值器,来修改。如果没有设置插值器,那么fraction的变化就是匀速的。
//经过插值器的计算,fraction的变化就会呈现出加速、减速变化的效果。
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//PropertyValuesHolder[] mValues,因为一个View可以有多个属性动画,所以这用一个数组来存储。
mValues[i].calculateValue(fraction);
}
}
AccelerateDecelerateInterpolator 插值器
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
void calculateValue(float fraction) {
//mKeyframes 就是前面创建的关键帧集合KeyframeSet
Object value = mKeyframes.getValue(fraction);
// 将得到的值,赋值给mAnimatedValue
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
下面这个方法是真正去计算改变后的属性值。通过估值器mEvaluator去计算的。
public Object getValue(float fraction) {
//第一关键帧记做前一关键帧
Keyframe prevKeyframe = mFirstKeyframe;
for (int i = 1; i < mNumKeyframes; ++i) {
//得到下一关键帧
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
//得到前一关键帧,对应的部分
final float prevFraction = prevKeyframe.getFraction();
//fraction - prevFraction 当前要执行的部分距离前一关键帧是多少。
//nextKeyframe.getFraction() - prevFraction,这一帧有多少
//两者相除,得到的就是当前部分在这一帧的占比
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
if (interpolator != null) {
//通过插值器来修改,这一部分的大小
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
//通过估值器,来计算属性值要变化到多少
//这个估值器就是上面赋值的FloatEvaluator或IntEvaluator
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
nextKeyframe.getValue());
}
prevKeyframe = nextKeyframe;
}
// shouldn't reach here
//不应该执行到这里,在上面的for循环就应该返回当前动画,属性变化的大小。
return mLastKeyframe.getValue();
}
通过估值器计算view的属性值。
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
//通过一个一元一次方程,来计算得到当前的属性值。
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
至此,动画要变动后的属性值,已经计算出来了,
通过 mValues[i].setAnimatedValue(target);用来修改View的属性值大小。
void setAnimatedValue(Object target) {
//前面已经通过反射拿到了View的setter方法
if (mSetter != null) {
try {
//拿到属性值大小,
mTmpValueArray[0] = getAnimatedValue();
//通过反射,修改view属性值的大小
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
Object getAnimatedValue() {
return mAnimatedValue;
}
至此,android属性动画的整个执行流程已经分析完毕。
可以总结以下几点:
1.ValueAnimator是父类,ObjectAnimator是子类,这里面封装了一个target,也就是view对象。
2.PropertyValuesHolder,有属性名,属性值,通过属名来反射view的setter方法,来动态修改属性值。
3.KeyframeSet,是一个关键帧集合,封装了定义动画是value数组的值,每一个值都被记录为一个关键帧FloatKeyframe。
4.通过插值器,可以改变属性变化的快慢,通过估值器计算属性值的大小。
5.给Choreographer注册了一个回调,每隔16.66ms回调一次,每一次回调都会去改变view属性值的大小。改变是通过fraction计算的,进而通过计算得到改变后的属性值大小。
这样动态的改变view属性值的大小,就连贯的形成一幅动画。