Android属性动画源码解析(一)
ValueAnimator动画启动流程
1. 创建属性动画
private void initAnim() {
//最简单属性动画
simple = ObjectAnimator.ofFloat(0, 300);
simple.setDuration(1000);
simple.addUpdateListener(animation -> view.setTranslationX((Float) animation.getAnimatedValue()));
//直接操作对象属性的动画,propertyName在对象中要有和bean一样的get和set方法
property = ObjectAnimator.ofFloat(view, "translationX", 0, 300);
property.setDuration(1000);
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(300, 0);
path.lineTo(300, 300);
path.lineTo(0, 300);
path.close();
//path创建的动画
this.path = ObjectAnimator.ofFloat(view, "translationX", "translationY", path);
this.path.setDuration(2000);
//更新bean属性
customProperty = ObjectAnimator.ofFloat(new Data(), "value", 0, 300);
customProperty.setDuration(100);
}
static class Data {
private float value;
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
log.dMKV("setValue", "value", value);
}
}
这四个动画的效果如下:
2. 基于第一个动画的源码分析
首先来看动画创建,仅仅创建了一个ValueAnimator,构造器什么都没做:
public void setFloatValues(float... values) {
...
if (mValues == null || mValues.length == 0) {
setValues(PropertyValuesHolder.ofFloat("", values));
} else {
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setFloatValues(values);
}
...
}
新创建动画,直接走setValues,先来看PropertyValuesHolder.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;
}
}
}
if (badValue) {
Log.w("Animator", "Bad value (NaN) in float animator");
}
return new FloatKeyframeSet(keyframes);
}
如果仅一个参数,会在前面补mFraction和mValue都是0的Keyframe,从else可以知道,values.length为3以上时,mFraction会均分赋值,比如:如果values是0,1,2,3,四个数分成三段,fraction分别是0,1/3,2/3,1。
再回到setValues,实际上就赋值了一个PropertyValuesHolder参数,实际上是可以有多个参数,path的动画就有两个参数,但是getAnimatedValue等方法仅操作第一个PropertyValuesHolder,需要注意使用。
接下来,来看动画的启动
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
...
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
startAnimation();
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
首先检查了有没有handler,这是因为编舞者需要handler,并没有检查主线程,属性动画可以在子线程执行,当然更新UI还是不行,没有调度线程的能力,得开发者自己搞。
addAnimationCallback是一个关键方法,它向AnimationHandler注册了一个回调,AnimationHandler本质是对编舞者(Choreographer)的一个简单封装,AnimationHandler和Choreographer都不是真正的单例,而是用ThreadLocal修饰的一个相对于线程的单例,Choreographer利用SurfaceFlinger的脉冲信号或Handler的延时信息来达成一定频率的回调,让动画得以执行。
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));
}
}
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;
}
}
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
@Override
public long getFrameTime() {
return mChoreographer.getFrameTime();
}
}
编舞者部分代码:
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long 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);
}
}
}
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
final long startNanos;
final long frameIntervalNanos = vsyncEventData.frameInterval;
boolean resynced = false;
try {
FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData);
synchronized (mLock) {
long intendedFrameTimeNanos = frameTimeNanos;
startNanos = System.nanoTime();
final long jitterNanos = startNanos - frameTimeNanos;
if (jitterNanos >= frameIntervalNanos) {
frameTimeNanos = startNanos;
if (frameIntervalNanos == 0) {
Log.i(TAG, "Vsync data empty due to timeout");
} else {
long lastFrameOffset = jitterNanos % frameIntervalNanos;
frameTimeNanos = frameTimeNanos - lastFrameOffset;
final long skippedFrames = jitterNanos / frameIntervalNanos;
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main "
+ "thread.");
}
}
timeline = mFrameData.update(
frameTimeNanos, mDisplayEventReceiver, jitterNanos);
resynced = true;
}
...
mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos,
vsyncEventData.preferredFrameTimeline().vsyncId,
vsyncEventData.preferredFrameTimeline().deadline, startNanos,
vsyncEventData.frameInterval);
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
mLastFrameIntervalNanos = frameIntervalNanos;
mLastVsyncEventData.copyFrom(vsyncEventData);
}
if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
String message = String.format("Choreographer#doFrame - resynced to %d in %.1fms",
timeline.mVsyncId, (timeline.mDeadlineNanos - startNanos) * 0.000001f);
Trace.traceBegin(Trace.TRACE_TAG_VIEW, message);
}
AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
mFrameInfo.markInputHandlingStart();
doCallbacks(Choreographer.CALLBACK_INPUT, frameIntervalNanos);
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameIntervalNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameIntervalNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos);
} finally {
AnimationUtils.unlockAnimationClock();
if (resynced) {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
上面注册使用的callbackType是CALLBACK_ANIMATION,编舞者定义了5种type,各有各的执行顺序,mCallbackQueues是一个队列(是一个链表,类似Message的回收队列)数组,每种type都有一个队列。
USE_VSYNC为true代表使用SurfaceFilinger来驱动,false意味着通过handler延时消息来驱动(延时是10ms,也就是理论上100fps),两种方式最后都会走到doFrame方法。这里对帧数据尤其是时间进行了一些列的检查,其中有个主线程做太多事的log不少开发者都见过(ViewRootImpl里面也有一个编舞者),CALLBACK_INPUT等5种类型的事件也在这里回调。
回到start方法,看startAnimation方法,其调用了initAnimation,并回调了notifyStartListeners,AnimatorListener::onAnimationStart就是在这里回调。initAnimation调用了PropertyValuesHolder的init方法。
void init() {
if (mEvaluator == null) {
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator :
null;
}
if (mEvaluator != null) {
mKeyframes.setEvaluator(mEvaluator);
}
}
init方法给mKeyframes放入了估值器,也就是给KeyframeSet添加估值器,这里非常重要,估值器真正起作用的地方就在这里。
再次回到start方法,看setCurrentPlayTime(0)
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);
animateValue(currentIterationFraction);
}
private float getCurrentIterationFraction(float fraction, boolean inReverse) {
fraction = clampFraction(fraction);
int iteration = getCurrentIteration(fraction);
float currentFraction = fraction - iteration;
return shouldPlayBackward(iteration, inReverse) ? 1f - currentFraction : currentFraction;
}
再次init动画,之前已经init了,这个忽略,getCurrentIterationFraction方法通过计算,将动画分为一段一段,每一段都是0~1,此方法也通过shouldPlayBackward简单的完成了动画的反向执行,接下来来看核心方法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 (mSeekFraction >= 0 || mStartListenersCalled) {
callOnList(mUpdateListeners, AnimatorCaller.ON_UPDATE, this, false);
}
}
在这里看到了插值器,它计算出了新的fraction,然后将这个值传入PropertyValuesHolder.calculateValue,在这之后通过callOnList调用了ON_UPDATE回调了AnimatorUpdateListener.onAnimationUpdate
void calculateValue(float fraction) {
Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
之前init的时候,将估值器传入了mKeyframes,现在把通过插值器计算后的值给到mKeyframes并获取Value值(这里直接看通用的方法,Float的方法和这个其实没有区别,计算方式完全一样),这个就是getAnimatedValue获取到的值。
public Object getValue(float fraction) {
if (mNumKeyframes == 2) {
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
return mEvaluator.evaluate(fraction, mFirstKeyframe.getValue(),
mLastKeyframe.getValue());
}
if (fraction <= 0f) {
final Keyframe nextKeyframe = mKeyframes.get(1);
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
final float prevFraction = mFirstKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
return mEvaluator.evaluate(intervalFraction, mFirstKeyframe.getValue(),
nextKeyframe.getValue());
} else if (fraction >= 1f) {
final Keyframe prevKeyframe = mKeyframes.get(mNumKeyframes - 2);
final TimeInterpolator interpolator = mLastKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
final float prevFraction = prevKeyframe.getFraction();
float intervalFraction = (fraction - prevFraction) /
(mLastKeyframe.getFraction() - prevFraction);
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
mLastKeyframe.getValue());
}
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();
float intervalFraction = (fraction - prevFraction) /
(nextKeyframe.getFraction() - prevFraction);
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
nextKeyframe.getValue());
}
prevKeyframe = nextKeyframe;
}
return mLastKeyframe.getValue();
}
这里可以明显看出插值器的输出值是如何被使用的,mKeyframes的各个Fraction值是用1均分而来,插值器的返回值是不定的,可以是负数,也可以大于1,因此有了如上的两个判断。这里用默认插值器来看Value的变化。默认插值器是AccelerateDecelerateInterpolator,数值变化如下:
这是一个三角函数变化曲线,减去0.5再乘以2就是标准曲线,大致如右图。如果是第一个动画,那Value值的变化就是如此(Fraction*300即可),如果ofFloat传入了多个值,比如:0,1,2,3,比如1~2之间,intervalFraction是(fraction - 1/3) / (2/3 - 1/3),而由于插值器的干预,fraction到这个范围明显只有中间几个数(0.34~0.65),也就是前半段和后半段占用的运行时间段都比中段多,前后都花了更多的时间执行,也就是前后View表现的运动状态是慢一些的,中间比较快。
intervalFraction的计算是在这个分段计算比率(0~1),估值器也不用是线性估值,可以针对每一段执行不同的估值逻辑,让动画更多样更复杂。setValues的时候将将1平均分成values.length - 1段来赋值每个KeyFrame,这里将每一段都看成独立部分,每一段都是0~1范围的fraction值。
可以看到这里也存在一个插值器,我们自己创建的动画都是null,不存在插值器的二次计算,但是这个并不是没有用的,在AnimatorInflater有相应的插值器的导入,也就是xml写的属性动画。比如如下动画,当然这样加载出来的动画仍然存在ValueAnimator中的默认插值器,通过xml.setInterpolator(null)可以把默认插值器改成线性插值器,其实就相当于没有插值器。keyframe的插值器都是用next的插值器,第一个就不用设置插值器,那个是不会被使用的
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000">
<propertyValuesHolder
android:propertyName="translationX"
android:valueType="floatType">
<keyframe
android:fraction="0"
android:value="0" />
<keyframe
android:fraction="0.5"
android:interpolator="@android:anim/cycle_interpolator"
android:value="150" />
<keyframe
android:fraction="1"
android:interpolator="@android:anim/linear_interpolator"
android:value="300" />
</propertyValuesHolder>
</objectAnimator>
xml = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.test);
xml.setInterpolator(null);
xml.setTarget(view);
动画的启动就完成了,后续动画更新由编舞者驱动,具体方法如下:
public final boolean doAnimationFrame(long frameTime) {
...
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) {
done = true;
} else if (newIteration && !lastIterationFinished) {
notifyListeners(AnimatorCaller.ON_REPEAT, false);
} else if (lastIterationFinished) {
done = true;
}
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}
最后还是回到了animateValue,参数计算方式没什么改变,getScaledDuration是一个开发者不可修改的值,但是用户可以在开发者模式中关闭动画,它的值就会变成0,所有动画都会执行的瞬间就结束,最原始的fraction是(当前时间-启动时间)/ duration,动画是可以多次反复执行的,因此fraction是能大于1的,所以需要getCurrentIterationFraction来把值限定到0~1之间。animateBasedOnTime的返回值为true时,动画就执行完毕了。
这里有ON_REPEAT的回调,其它几个就不说了,很容易找。
3. ObjectAnimator直接更新对象属性的原理
首先来看动画创建,仅仅赋了值,啥都没干
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);
setPropertyName(propertyName);
}
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;
}
继续看ObjectAnimator的setFloatValues,和之前的没有任何区别,还是FloatPropertyValuesHolder,只是之前的mPropertyName是""
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);
}
}
继续看启动,调到关键代码处
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) {
mValues[i].setAnimatedValue(target);
}
}
获取到值后,调用setAnimatedValue方法对对象进行赋值
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 {
mTmpValueArray[0] = mFloatAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
第四个动画是传入了PropertyName,因此是使用反射,FloatPropertyValuesHolder的反射在native进行,因此是mJniSetter,相关反射在initAnimation执行
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();
}
}
这种方式调用的动画是有限制的,propertyName首字母大写以后,拼接set和get在后的方法名必须在对象中找到。
4. path动画
Path动画其实就是一组多点float数据
public PathKeyframes(Path path, float error) {
if (path == null || path.isEmpty()) {
throw new IllegalArgumentException("The path must not be null or empty");
}
mKeyframeData = path.approximate(error);
}
approximate方法会返回很多个点,这些点构成了Path路径
public Object getValue(float fraction) {
int numPoints = mKeyframeData.length / 3;
if (fraction < 0) {
return interpolateInRange(fraction, 0, 1);
} else if (fraction > 1) {
return interpolateInRange(fraction, numPoints - 2, numPoints - 1);
} else if (fraction == 0) {
return pointForIndex(0);
} else if (fraction == 1) {
return pointForIndex(numPoints - 1);
} else {
// Binary search for the correct section
int low = 0;
int high = numPoints - 1;
while (low <= high) {
int mid = (low + high) / 2;
float midFraction = mKeyframeData[(mid * NUM_COMPONENTS) + FRACTION_OFFSET];
if (fraction < midFraction) {
high = mid - 1;
} else if (fraction > midFraction) {
low = mid + 1;
} else {
return pointForIndex(mid);
}
}
// now high is below the fraction and low is above the fraction
return interpolateInRange(fraction, high, low);
}
}
private PointF interpolateInRange(float fraction, int startIndex, int endIndex) {
int startBase = (startIndex * NUM_COMPONENTS);
int endBase = (endIndex * NUM_COMPONENTS);
float startFraction = mKeyframeData[startBase + FRACTION_OFFSET];
float endFraction = mKeyframeData[endBase + FRACTION_OFFSET];
float intervalFraction = (fraction - startFraction)/(endFraction - startFraction);
float startX = mKeyframeData[startBase + X_OFFSET];
float endX = mKeyframeData[endBase + X_OFFSET];
float startY = mKeyframeData[startBase + Y_OFFSET];
float endY = mKeyframeData[endBase + Y_OFFSET];
float x = interpolate(intervalFraction, startX, endX);
float y = interpolate(intervalFraction, startY, endY);
mTempPointF.set(x, y);
return mTempPointF;
}
通过二分法查找相应的点,如果没找到,找到相邻的两个点,计算比率,然后按比率来获取点的值,和最上面的getValue是同一种计算方式,PathKeyframes没有估值器,它的职能决定了它不允许开发者进一步的调整。
查看之前ObjectAnimator.animateValue的源码可以知道,animateValue会遍历PropertyValuesHolder列表,每一个都进行赋值,再看Path创建时
public static ObjectAnimator ofFloat(Object target, String xPropertyName, String yPropertyName,
Path path) {
PathKeyframes keyframes = KeyframeSet.ofPath(path);
PropertyValuesHolder x = PropertyValuesHolder.ofKeyframes(xPropertyName,
keyframes.createXFloatKeyframes());
PropertyValuesHolder y = PropertyValuesHolder.ofKeyframes(yPropertyName,
keyframes.createYFloatKeyframes());
return ofPropertyValuesHolder(target, x, y);
}
public FloatKeyframes createXFloatKeyframes() {
return new FloatKeyframesBase() {
@Override
public float getFloatValue(float fraction) {
PointF pointF = (PointF) PathKeyframes.this.getValue(fraction);
return pointF.x;
}
};
}
static PropertyValuesHolder ofKeyframes(String propertyName, Keyframes keyframes) {
if (keyframes instanceof Keyframes.IntKeyframes) {
...
} else if (keyframes instanceof Keyframes.FloatKeyframes) {
return new FloatPropertyValuesHolder(propertyName,
(Keyframes.FloatKeyframes) keyframes);
} else {
...
}
}
实际上是创建了FloatPropertyValuesHolder,然后又是propertyName那套逻辑,和上面的动画2没区别,获取值时是调用的FloatKeyframesBase的getFloatValue,而它的值是PathKeyframes.getValue提供。
5. Property
使用propertyName来直接操作对象是创建动画常用的方式,但是这里有个问题,它需要反射,从源码也可以看出,谷歌特意使用了native来进行反射,进一步提速,其实是可以避免反射的,自定义Property即可。
propertyTest = ObjectAnimator.ofFloat(new Data(), new Property<Data, Float>(Float.class, "value") {
@Override
public Float get(Data data) {
return data.value;
}
@Override
public void set(Data data, Float value) {
log.dMKV("custom property: setValue", "value", value);
data.setValue(value);
}
}, 0, 300);
这个Property同样会赋值到FloatPropertyValuesHolder,再看setAnimatedValue,直接调用了set方法。
void setAnimatedValue(Object target) {
if (mFloatProperty != null) {
mFloatProperty.setValue(target, mFloatAnimatedValue);
return;
}
if (mProperty != null) {
mProperty.set(target, mFloatAnimatedValue);
return;
}
}
Property还存在一个特殊子类ReflectiveProperty
public static <T, V> Property<T, V> of(Class<T> hostType, Class<V> valueType, String name) {
return new ReflectiveProperty<T, V>(hostType, valueType, name);
}
public void set(T object, V value) {
if (mSetter != null) {
try {
mSetter.invoke(object, value);
} catch (IllegalAccessException e) {
throw new AssertionError();
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
} else if (mField != null) {
try {
mField.set(object, value);
} catch (IllegalAccessException e) {
throw new AssertionError();
}
} else {
throw new UnsupportedOperationException("Property " + getName() +" is read-only");
}
}
@Override
public V get(T object) {
if (mGetter != null) {
try {
return (V) mGetter.invoke(object, (Object[])null);
} catch (IllegalAccessException e) {
throw new AssertionError();
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
} else if (mField != null) {
try {
return (V) mField.get(object);
} catch (IllegalAccessException e) {
throw new AssertionError();
}
}
// Should not get here: there should always be a non-null getter or field
throw new AssertionError();
}
该类本质上和propertyName没有任何区别,只是将PropertyValuesHolder中的反射移植到了这里,多了一个is开头的get方法支持,多了直接操作属性,暂时看不出这种方式有什么其它优势。
6. TypeConverter
属性动画还提供了数据转换器,先看一个例子
TypeEvaluator<PointF> typeEvaluator = new TypeEvaluator<PointF>() {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
float x = startValue.x + (endValue.x - startValue.x) * fraction;
float y = startValue.y + (endValue.y - startValue.y) * fraction;
return new PointF(x, y);
}
};
Property<TypeCovertData, float[]> property1 = new Property<TypeCovertData, float[]>(float[].class, "Points") {
@Override
public float[] get(TypeCovertData object) {
return object.points;
}
@Override
public void set(TypeCovertData object, float[] value) {
object.setPoints(value);
}
};
TypeConverter<PointF, float[]> tc = new TypeConverter<PointF, float[]>(PointF.class, float[].class) {
@Override
public float[] convert(PointF value) {
return new float[]{value.x, value.y};
}
};
typeCovert = ObjectAnimator.ofObject(new TypeCovertData(), property1, tc, typeEvaluator, new PointF(0, 0), new PointF(200, 300));
static class TypeCovertData {
private float[] points = new float[2];
public float[] getPoints() {
return points;
}
public void setPoints(float[] points) {
this.points = points;
log.dMKV("setPoints", "points", Arrays.toString(points));
}
}
可以将数据转换成另一种格式,也可以再计算,比如获取坐标乘积,还比如,如果你想修改view背景色和背景色的透明度,但是其背景色和透明度变化规律不一样,需要分开计算,可以在TypeEvaluator里面分开计算数值,可以在TypeConverter里面合并成一个颜色,最后直接赋值给View背景色就行了。
数据转换器的作用位置在PropertyValuesHolder,计算值之后就转换好了
void calculateValue(float fraction) {
Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
上面几个参数调用顺序如下:
7. 总结
- 属性动画执行的线程必须有handler;
- 属性动画可以在子线程执行,但在子线程执行无法直接更新UI;
- 属性动画估值器的fraction在每一段(比如:ofFloat传入多个值,有values.length - 1段)都是0~1;
- 可以通过xml给keyframe赋予插值器,让不同的阶段执行不同的插值器,代码也可以实现这个效果,只要自定义插值器,并在不同的阶段使用不同的计算方式就行了,阶段通过fraction判断即可;
- 属性动画start所在的线程就是之后执行的线程,也是编舞者调度的线程;
- 利用propertyName对对象部分值直接更新的方式存在限制,propertyName首字母大写以后,拼接set和get后的字符串要在对象中找得到对应的方法;
- 不是所有的动画的估值器都是有效的,ofFloat如果添加了Path,估值器就无效;
- 使用Property相对于propertyName有一定性能提升,可以避免反射,也不限制方法名;
本文对Android属性动画中ValueAnimator的启动流程进行了详细解析。包括创建属性动画、基于源码分析动画启动过程,如检查handler、注册回调等。还阐述了ObjectAnimator更新对象属性原理、path动画特点,以及Property、TypeConverter的作用,并对属性动画的执行特点进行了总结。
4万+

被折叠的 条评论
为什么被折叠?



