好久没有写博客,因为本身工作的项目里面涉及到很多动画,而里面用到动画基本是属性动画,所以这几天就认真阅读了里面的源码进行分析,话不多说,开讲!
使用
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, 2.0f);
animator.setDuration(2000);
animator.setInterpolator(new LinearInterpolator());
animator.start();
分析
-
前期准备工作
首先我们来看看ofFloat方法
#ObjectAnimator.ofFloat()
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
这里首先会传入三个参数,target就是要执行动画的View,propertyName就是改变的属性名字,随后values是一个可变数组,其实可以理解为属性变化的值,从后面分析可以知道这里是关键帧。接下来我们看ObjectAnimator的构造方法:
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);
setPropertyName(propertyName);
}
public void setTarget(@Nullable Object target) {
final Object oldTarget = getTarget();
// 如果旧的动画与新的动画设置的对象是一样的,如果旧动画已经开始了
// 则需要把旧动画取消
if (oldTarget != target) {
if (isStarted()) {
cancel();
}
mTarget = target == null ? null : new WeakReference<Object>(target);
mInitialized = false;
}
}
public void setPropertyName(@NonNull String propertyName) {
if (mValues != null) {
PropertyValuesHolder valuesHolder = mValues[0];
String oldName = valuesHolder.getPropertyName();
valuesHolder.setPropertyName(propertyName);
mValuesMap.remove(oldName);
mValuesMap.put(propertyName, valuesHolder);
}
mPropertyName = propertyName;
mInitialized = false;
}
这个构造方法里面,最主要的就是赋值,mTarget 为控件对象,mPropertyName为修改的属性。我们继续看回ObjectAnimator.ofFloat方法里面,还会调用anim.setFloatValues(values),我们继续看看:
#ObjectAnimator.setFloatValues()
public void setFloatValues(float... values) {
if (mValues == null || mValues.length == 0) {
if (mProperty != null) {
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
} else {
// 关键代码1
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}
因为在此之前mValues并没有进行赋值,而且mProperty也是为空的,所以会走到关键代码1这里setValues(PropertyValuesHolder.ofFloat(mPropertyName, values)),那我们就先看PropertyValuesHolder.ofFloat()这个方法
#PropertyValuesHolder.ofFloat
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
return new FloatPropertyValuesHolder(propertyName, values);
}
private PropertyValuesHolder(String propertyName) {
mPropertyName = propertyName;
}
// FloatPropertyValuesHolder类里面的
public FloatPropertyValuesHolder(String propertyName, float... values) {
super(propertyName);
setFloatValues(values);
}
我们可以看到,这里返回的FloatPropertyValuesHolder的实例,而FloatPropertyValuesHolder是继承自PropertyValuesHolder ,所以我们看到它的构造方法这里。
在FloatPropertyValuesHolder的构造方法里,会调用super(proertyName),就是将PropertyValuesHolder中的mPropertyName属性名赋值为传进来的proertyName(注:需要变化的属性)。
接着FloatPropertyValuesHolder的构造方法里面,还调用了setFloatValues(values),我们看看:
#FloatPropertyValuesHolder.setFloatValues()
public void setFloatValues(float... values) {
super.setFloatValues(values);
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
// PropertyValuesHolder.setFloatValues()
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}
这里我们可以看到它这里又会去调用父类PropertyValuesHolder的setFloatValues方法,然后将mKeyframes赋值给mFloatKeyframes。
那我们就看看父类的setFloatValues方法,这里首先将mValueType赋值为float.class,就是说修改的属性值是float类型的。然后就是调用KeyframeSet.ofFloat方法
#KeyframeSet.ofFloat
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
// 动画至少两个关键帧,一头一尾
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
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) {
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
return new FloatKeyframeSet(keyframes);
}
// fraction:当前关键帧的时间百分比,例如有3帧,那么第2帧就是50%了
// value:来到当前帧时的数值
public static Keyframe ofFloat(float fraction, float value) {
return new FloatKeyframe(fraction, value);
}
这里可以看到就是创建我们一个一个的关键帧Keyframe,因为修改的属性类型是float类型,所以会转化为FloatKeyframe。然后把这些关键帧封装到关键帧集FloatKeyframeSet中,然后返回到PropertyValuesHolder的时候是mKeyframes ,到FloatPropertyValuesHolder就会强转为mFloatKeyframes。
// 关键帧
public interface Keyframes extends Cloneable
//关键帧集,管理着关键帧
public class KeyframeSet implements Keyframes {
int mNumKeyframes;
Keyframe mFirstKeyframe;
Keyframe mLastKeyframe;
TimeInterpolator mInterpolator;
List<Keyframe> mKeyframes;
TypeEvaluator mEvaluator;
}
// Float类型的关键帧集
class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes
至此,动画的准备工作已经处理好了,我们就正式启动动画。
-
动画启动
#ObjectAnimator.start
public void start() {
AnimationHandler.getInstance().autoCancelBasedOn(this);
......
super.start();
}
这里主要的是调用了父类ValueAnimator的start方法:
#ValueAnimator.start
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("");
}
......
// 关键代码1
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
// 关键代码2
startAnimation();
if (mSeekFraction == -1) {
// 关键代码3
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
这里看到调用ValueAniator.start()方法之后,又调用了start的重载方法。我们看这里的关键代码1,addAnimationCallback(0),就是会把自身ValueAnimator添加到回调中,因为ValueAnimator是实现了AnimationHandler.AnimationFrameCallback这个接口,这里后续会继续讲到。
然后我们继续看到关键代码2这里,startAnimation()方法
#ValueAnimator.startAnimation()
private void startAnimation() {
......
initAnimation();
......
if (mListeners != null) {
// 通知注册了动画监听器的回调
notifyStartListeners();
}
}
这里主要是两行代码,就是initAnimation和notifyStartListeners。
#ValueAnimator.initAnimation()
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
// 这个mValues数组就是PropertyValuesHolder数组
// 在前面也定义过,这里就是FloatPropertyValuesHolder
mValues[i].init();
}
mInitialized = true;
}
}
在前面我们已经得知这个mValues数组就是PropertyValuesHolder数组,mValues[i]就是FloatPropertyValuesHolder,那我们看看init方法
#PropertyValuesHolder.init()
void init() {
if (mEvaluator == null)
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator :
null;
}
if (mEvaluator != null) {
mKeyframes.setEvaluator(mEvaluator);
}
}
这个方法的主要目的就是给关键帧设置估值器。因为类型是float类型,所以估值器就是sFloatEvaluator,也就是FloatEvaluator。
#KeyframeSet.setEvaluator()
public void setEvaluator(TypeEvaluator evaluator) {
mEvaluator = evaluator;
}
现在我们继续回到ValueAnimator.start()方法里面,看到关键代码3这里setCurrentPlayTime(0)
#ValueAnimator.setCurrentPlayTime()&setCurrentFraction()
public void setCurrentPlayTime(long playTime) {
// 因为动画的时长比0大,而且一开始playTime是为0的
// 所以传进去的是0
float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
setCurrentFraction(fraction);
}
public void setCurrentFraction(float fraction) {
initAnimation();
fraction = clampFraction(fraction);
mStartTimeCommitted = true;
if (isPulsingInternal()) {
long seekTime = (long) (getScaledDuration() * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
mStartTime = currentTime - seekTime;
} else {
mSeekFraction = fraction;
}
mOverallFraction = fraction;
final float currentIterationFraction =
getCurrentIterationFraction(fraction, mReversing);
// 执行动画,在子类和父类中都有定义这个方法,
// 因此是会调用子类ObjectAnimator.animateValue方法
animateValue(currentIterationFraction);
}
好,这里看注释应该基本都清楚,那我们就看看子类和父类的animateValue()方法
#ObjectAnimator.animateValue() & ValueAnimator.animateValue()
/**子类
* ObjectAnimator.animateValue()
*/
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) {
// 通过holder来修改target的属性值
mValues[i].setAnimatedValue(target);
}
}
/** 父类
* ValueAnimator.animateValue()
*/
void animateValue(float fraction) {
// 通过插值器计算当前这一帧所占用的时间百分比
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//通过holder,根据插值,用估值器计算这一帧的属性值
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
这里首先调用的是子类的animateValue方法,然后在里面又调用了父类的animateValue方法。
在父类中,一开始就利用时间插值器计算这一帧的时间百分比,然后在利用估值器计算出当前帧的属性数值,mValues[i].calculateValue(fraction):
void calculateValue(float fraction) {
mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}
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();
}
FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
// Apply interpolator on the proportional duration.
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
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();
}
算出这一帧的属性值之后,就要去修改属性值,所以我们就要看回ObjectAnimator.animateValue()方法里面,会调用holder的setAnimatedValue(target)方法来修改属性值
#FloatPropertyValuesHolder.setAnimatedValue(target)
void setAnimatedValue(Object target) {
if (mFloatProperty != null) {
mFloatProperty.setValue(target, mFloatAnimatedValue);
return;
}
if (mProperty != null) {
mProperty.set(target, mFloatAnimatedValue);
return;
}
if (mSetter != null) {
try {
mTmpValueArray[0] = mFloatAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
很清楚这里就是通过通过反射设置数值,修改数值。
总结这个animateValue方法:
1、通过插值器计算出到这帧的时候需要占时间的百分比
2、根据这个时间百分比,利用估值器算出此时控件的属性值
3、最后在FloatPropertyValuesHolder里面,利用反射设置控件的属性值
-
如何反复调用改变控件的属性,进而形成动画?
通过以上的操作,一帧就处理了,但是后面的帧数怎么办呢?
其实在start方法的时候,有注册了一个AnimationFrameCallback,而这个callback就是ValueAnimator,接口定义了一个方法,就是doAnimationFrame,
AnimationHandler.addAnimationFrameCallback(callback, delay)
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
......
}
#ValueAnimator.doAnimationFrame()
public final boolean doAnimationFrame(long frameTime) {
......
final long currentTime = Math.max(frameTime, mStartTime);
// 关键代码
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
return finished;
}
这里只需要看关键代码animateBasedOnTime()
#ValueAnimator.animateBasedOnTime()
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
......
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
这里首先也是计算fraction时间百分比,然后往下看,看到没有,又调用了animateValue()方法了,这个方法在上面就已经讲过了。所以只要doAnimationFrame方法不断被调用,属性值就不断地发生变化,就会产生动画的效果。
那么如何连续调用doAnimationFrame方法?
在ValueAnimator的start方法里面,注册回调接口的时候,调用到AnimationHandler.addAnimationFrameCallback方法的时候,有这样一行getProvider().postFrameCallback(mFrameCallback);
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
......
}
这个mFrameCallback,我们来看看:
private final Choreographer.FrameCallback mFrameCallback =
new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
......
}
};
在Android系统中,每隔16ms就会发出VSYNC信号,触发对UI的渲染,然后就会调用Choreographe的回调接口,就是说会调用这个doFrame的回调函数。在这个函数里面,又会调用doAnimationFrame(long frameTime)这个方法:
#AnimationHandler.doAnimationFrame(long frameTime)
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;
}
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
......
}
}
cleanUpList();
}
这里我们看到会遍历这个mAnimationCallbacks的集合,它其实是在Animator.start()方法里面addAnimationFrameCallback()的时候添加callback,而这个callback其实就是ValueAnimator,所以就会去调用ValueAnimator的doAnimationFrame()方法。这样就可以造成多次调用doAnimationFrame方法,进而形成动画。
补充:
插值器与估值器的区别?
这里有个透明度从0f到1f的动画,需要5帧就能完成。这里每一帧出现的时间都不会发生改变,都是每隔16ms会画一帧出来,但是每一帧占这个动画总的百分比不一样,这里就是通过插值器来控制的。拿到这个百分比之后,就可以通过估值器算出这个百分比占的数值是多少
插值器:相当于函数y=f(x),x为时间,y为时间的百分比,0~1之间
估值器:根据时间变化规律得到每一步的运算结果