ValueAnimator属性动画深入解析

前言

在上一篇博文Handler消息机制深入解析当中,在最后面说到:

ViewAnimation底层也是通过调用invalidate来实现的,无限循环动画就会导致无限调用invalidate,就会导致idle得不到执行。
属性动画的实现原理不同于View动画。View动画的每一帧都是通过invalidate方法来触发重绘,而属性动画每一帧的绘制都是通过Choreographer的回调实现。因此,本质上来说,属性动画少了一个很重要的步骤,就是post一个同步屏障。在属性动画中,没有同步屏障,那么后续的任务能够继续执行,当队列中没有任务时,自然就会回调IdleHandler了。

那么事实真的如此的么?今天我们就从源码的角度来对属性动画进行一个深入解析,源码基于API30

实践

使用过属性动画的同学应该都清楚,一般情况下我们会和两个类打交道:ValueAnimator以及ObjectAnimator

1
2
3
ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback

ObjectAnimator extends ValueAnimator

可以看出ObjectAnimator是继承自ValueAnimator的,对应的常见使用方式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//ValueAnimator使用方式
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, (float) (view.getHeight() * 3));
valueAnimator.setDuration(500);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedValue = (float) valueAnimator.getAnimatedValue();
view.setTranslationY(animatedValue);
view.setTranslationX(animatedValue);
}
});
valueAnimator.start();

//ObjectAnimator使用方式
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 0.0f, 1f);
objectAnimator.setDuration(500);
objectAnimator.start();

可以看出对于ValueAnimator来说,是通过监听onAnimationUpdate回调,在回调里面根据当前的animatedValue来动态改变View的属性来实现动画效果;对于ObjectAnimator来说,通过一系列静态方法比如ofFloat来创建ObjectAnimator的时候,就会传入View对象以及传入一个View的属性,然后调用start方法,属性动画就会通过动画自动改变传入View的属性值。

这里有一个需要注意的点,传入的这个View属性必须要有set方法,比如针对”alpha”属性,就必须要有setAlpha方法,为啥必须要有set方法?稍后会进行讲解

从上面的使用可以看出,要想使用属性动画来实现改变View属性的功能,那么属性动画框架就需要解决2个问题:

1、属性动画每一帧绘制onAnimationUpdate回调实现
2、对于ObjectAnimator来说,怎么做到根据传入的View属性来自动改变其对应的值

基于以上问题,我们先从ValueAnimator的start方法做为切入点开始分析

ValueAnimator#start

首先来看下start方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public void start() {
start(false);
}

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) {
// 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);
}
}
}

可以看到,start方法里面主要做了以下事情:

1、设置一些标记位,比如mStarted、mPaused、mRunning等
2、调用addAnimationCallback方法,看这个名字像是添加动画回调,还记得最开始说属性动画每一帧的绘制都是通过Choreographer的回调实现,难道就是这里面的逻辑?
3、满足一定条件之后,开始startAnimation启动动画

我们接下来看下addAnimationCallback方法:

ValueAnimator#addAnimationCallback

1
2
3
4
5
6
7
8
9
10
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}

public AnimationHandler getAnimationHandler() {
return mAnimationHandler != null ? mAnimationHandler : AnimationHandler.getInstance();
}

可以看出addAnimationCallback方法里面调用了AnimationHandler的addAnimationFrameCallback方法,这个AnimationHandler是一个单例,调用addAnimationFrameCallback的时候传入了this,从VauleAnimator的实现来看,是一个AnimationHandler.AnimationFrameCallback接口。

接下来进一步看下AnimationHandler.addAnimationFrameCallback方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
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().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}

if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}

private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}

从这个方法的注释可以看出,就是注册一个下一帧的回调,在这个方法里面,调用了getProvider().postFrameCallback方法,传入了一个Choreographer.FrameCallback回调,getProvider返回的是一个MyFrameCallbackProvider对象,然后会把传入的AnimationFrameCallback保存在一个mAnimationCallbacks列表里面

来看下MyFrameCallbackProvider.postFrameCallback方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 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);
}
}

可以看出,postFrameCallback方法里面调用了Choreographer的postFrameCallback方法,如果看过我上一篇博文:Handler消息机制深入解析同学看到这里应该就明白了,这个postFrameCallback里面就会去向底层注册一个Vsync信号,在收到Vsync信号之后就会执行传入的Choreographer.FrameCallback回调,感兴趣的同学可以继续深入看下,这里就不展开讲了。

当收到收到Choreographer.FrameCallback回调之后,回到AnimationHandler里面的mFrameCallback:

1
2
3
4
5
6
7
8
9
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};

可以看到,收到Choreographer.FrameCallback回调之后,会先调用doAnimationFrame方法,然后接着又会调用getProvider().postFrameCallback(this)方法开启新一轮的注册Vsync信号流程,这样就形成了一个循环,除非mAnimationCallbacks为空。当动画结束的时候就会移除mAnimationCallbacks里面对应的callback,这样就不再继续监听Choreographer的FrameCallback回调了

其实到这里我们就可以回答最开始提出的问题了:属性动画是通过监听Choreographer.FrameCallback来实现的,与View动画原理是不一样的

收到Choreographer.FrameCallback回调之后,我们进一步来看下是怎么作用到ValueAnimator上面去的,来看下doAnimationFrame方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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();
}

在doAnimationFrame方法里面会遍历之前保存的AnimationFrameCallback列表,调用其doAnimationFrame方法,前面我们说了ValueAnimator实现了AnimationFrameCallback接口,那么来看下ValueAnimator里面的doAnimationFrame方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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 * resolveDurationScale());
}

// 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;
}

在这个方法里面最后会调用animateBasedOnTime方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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;
}

可以看出这个方法最后会调用animateValue方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@CallSuper
@UnsupportedAppUsage
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);
}
}
}

在这个方法里面会做两件事情:

1、mValues数组是一个PropertyValuesHolder[],代表属性数组,调用PropertyValuesHolder的calculateValue方法之后,里面会更新PropertyValuesHolder里面属性对应的mAnimatedValue。那这个PropertyValuesHolder数组是什么时候生成的呢?来看ValueAnimator里面相应的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}

public void setIntValues(int... values) {
if (values == null || values.length == 0) {
return;
}
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofInt("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}

public void setValues(PropertyValuesHolder... values) {
int numValues = values.length;
mValues = values;
mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
for (int i = 0; i < numValues; ++i) {
PropertyValuesHolder valuesHolder = values[i];
mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
}
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}

可以看出在ValueAnimator的一系列of方法里面,就会去初始化这个PropertyValuesHolder数组

2、通过AnimatorUpdateListener通知给外部调用方

至此,我们已经分析出了属性动画每一帧绘制onAnimationUpdate回调实现方案:通过在start方法里面监听Choreographer的frameCallback来实现的

接下来看下第2个问题:

对于ObjectAnimator来说,怎么做到根据传入的View属性来自动改变其对应的值

对于这个问题,我们从ObjectAnimator.ofFloat做为切入点来分析:

ObjectAnimator#ofFloat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}

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);
// New target should cause re-initialization prior to starting
mInitialized = false;
}
}

public void setPropertyName(@NonNull String propertyName) {
// mValues could be null if this is being constructed piecemeal. Just record the
// propertyName to be used later when setValues() is called if so.
if (mValues != null) {
PropertyValuesHolder valuesHolder = mValues[0];
String oldName = valuesHolder.getPropertyName();
valuesHolder.setPropertyName(propertyName);
mValuesMap.remove(oldName);
mValuesMap.put(propertyName, valuesHolder);
}
mPropertyName = propertyName;
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}

从以上代码可以看出,ofFloat方法里面首先会根据传入的target以及propertyName构造一个ObjectAnimator对象,在构造方法里面会调用setTarget保存传入的View对象,调用setPropertyName方法会把propertyName保存起来,然后调用ObjectAnimator的setFloatValues方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void setFloatValues(float... values) {
if (mValues == null || mValues.length == 0) {
// No values yet - this animator is being constructed piecemeal. Init the values with
// whatever the current propertyName is
if (mProperty != null) {
setValues(PropertyValuesHolder.ofFloat(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}

因为这个时候mValues还没有赋值,并且也没有对mProperty字段进行赋值,那么就走到了
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values))这一行,这里通过调用
PropertyValuesHolder.ofFloat(mPropertyName, values)生成一个PropertyValuesHolder对象,然后调用setValues方法给前面提到的ValueAnimator里面的mValues属性数组进行赋值。

前面说到了ValueAnimator最终会调用到animateValue方法,在这个方法里面会先计算当前的属性值,然后通过AnimatorUpdateListener接口通知给外部:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@CallSuper
@UnsupportedAppUsage
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);
}
}
}

ObjectAnimator覆写了这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 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) {
mValues[i].setAnimatedValue(target);
}
}

在ObjectAnimator的animateValue方法里面会先调用ValueAnimator的animateValue方法计算好当前的属性值,然后会调用mValues数组里面每个PropertyValuesHolder的setAnimatedValue方法,传入target。到这里大家应该大概就可以猜到这个PropertyValuesHolder的setAnimatedValue方法里面肯定就是修改属性的地方了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 Method mSetter = null;

/**
* Internal function to set the value on the target object, using the setter set up
* earlier on this PropertyValuesHolder object. This function is called by ObjectAnimator
* to handle turning the value calculated by ValueAnimator into a value set on the object
* according to the name of the property.
* @param target The target object on which the value is set
*/
void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}

从这个方法可以看出,mSetter是一个Method对象,然后会通过invoke反射调用这个方法来修改属性的值。到这里,大家应该就比较明白了,ObjectAnimator里面最终是通过反射调用的方式来修改对应属性的值的。还记着在最开始说了传入的属性必须要有set方法么?说明属性只有具有set方法才能生成这个Method对象,那么这个Method是啥时候生成的呢?

ObjectAnimator#Method

通过追踪代码,最终发现在ObjectAnimation的initAnimation里面有如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@CallSuper
@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;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(target);
}
}
super.initAnimation();
}
}

这个initAnimation是在ValueAnimator的startAnimation方法里面调用的,在这个initAnimation方法里面会调用PropertyValuesHolder的setupSetterAndGetter方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void setupSetterAndGetter(Object target) {
//...............
// We can't just say 'else' here because the catch statement sets mProperty to null.
if (mProperty == null) {
Class targetClass = target.getClass();
if (mSetter == null) {
setupSetter(targetClass);
}
List<Keyframe> 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());
}
}
}
}
}

在上面代码当中会调用一个setupSetter方法,传入target的Class类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void setupSetter(Class targetClass) {
Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
}

private Method setupSetterOrGetter(Class targetClass,
HashMap<Class, HashMap<String, Method>> propertyMapMap,
String prefix, Class valueType) {
Method setterOrGetter = null;
synchronized(propertyMapMap) {
// Have to lock property map prior to reading it, to guard against
// another thread putting something in there after we've checked it
// but before we've added an entry to it
HashMap<String, Method> propertyMap = propertyMapMap.get(targetClass);
boolean wasInMap = false;
if (propertyMap != null) {
wasInMap = propertyMap.containsKey(mPropertyName);
if (wasInMap) {
setterOrGetter = propertyMap.get(mPropertyName);
}
}
if (!wasInMap) {
setterOrGetter = getPropertyFunction(targetClass, prefix, valueType);
if (propertyMap == null) {
propertyMap = new HashMap<String, Method>();
propertyMapMap.put(targetClass, propertyMap);
}
propertyMap.put(mPropertyName, setterOrGetter);
}
}
return setterOrGetter;
}

可以看出在setupSetter方法当中会调用setupSetterOrGetter方法,并且会传入一个前缀:”set”。在这个setupSetterOrGetter其实就是把:target、propertyName以及setterMethod三者映射起来,其中setterMethod是通过getPropertyFunction方法生成的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private Method getPropertyFunction(Class targetClass, String prefix, Class valueType) {
// TODO: faster implementation...
Method returnVal = null;
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
}
}
//.................
return returnVal;
}

static String getMethodName(String prefix, String propertyName) {
if (propertyName == null || propertyName.length() == 0) {
// shouldn't get here
return prefix;
}
char firstLetter = Character.toUpperCase(propertyName.charAt(0));
String theRest = propertyName.substring(1);
return prefix + firstLetter + theRest;
}

可以看出这个getMethodName的作用就是通过前缀和属性名字生成对应的方法。举个例子:

比如传入getMethodName(“set”,”alpha”),那么就会返回字符串:”setAlpha”,然后再通过
targetClass.getMethod(methodName, args)就可以得到具体的Method对象了,这个Method对象就会作用于前面说的PropertyValuesHolder的setAnimatedValue方法,用于改变target对应的属性值!

因此,这里就解释了为什么传给ObjectAnimator的属性一定要有对应的set方法了。

那么问题来了,如果传入的target的属性没有set方法,能不能使用属性动画呢?比如我们想通过动画来修改一个View宽度,那根据属性动画的原理的话,View就必须得有一个”setWidth”方法,但是实际上View并没有这个”setWidth”方法,那么还有没有其他办法呢?

答案是肯定的,因为这个set方法对应Method是通过反射自动生成的,因此,我们可以通过写一个Wrapper类,在这个Wrapper类里面定义一些set和get方法,然后这些set和get方法里面会去修改真正target的一些属性,然后做属性动画的时候传入这个Wrapper类即可,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class ViewWrapper {

private View mTarget;

public ViewWrapper(View target) {
mTarget = target;
}

public int getWidth() {
return mTarget.getLayoutParams().width;
}

public void setWidth(int width) {
android.view.ViewGroup.LayoutParams layoutParams = mTarget.getLayoutParams();
if (layoutParams != null) {
layoutParams.width = width;
mTarget.requestLayout();
}
}

public int getHeight() {
return mTarget.getLayoutParams().height;
}

public void setHeight(int height) {
android.view.ViewGroup.LayoutParams layoutParams = mTarget.getLayoutParams();
if (layoutParams != null && layoutParams.height != height) {
layoutParams.height = height;
mTarget.requestLayout();
}
}
}

zoomInAnimator = ObjectAnimatorUtils.ofInt(viewWrapper, "height", originalHeight, TITLE_BAR_HEIGHT);

通过这种方式,我们就可以通过动画来修改View的宽度和高度了,这样子是不是对属性动画的理解又深入了一层~~

总结

至此,属性动画源码分析就结束了,希望通过这篇文章能够让读者对属性动画能有一个更加深刻的认识~~