屬性動畫簡析
本文主要結合源碼闡述以下幾部分:
1.屬性值不斷變化是怎麼做到的?定時器?Handler?Choreographer?
2.值是怎麼設定到View上的?
3.插值器和估值器何時起作用的?
4.自定義插值器,估值器
一. 正常用法
可以使用ObjectAnimator,ValueAnimator,View 的animate(),AnimatorSet 等來實作,此處對如何使用不做展開。
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(item, "translationX", 0, 40, -30, 20, -10, 5, -5, 0);
objectAnimator.setDuration(1000);
objectAnimator.setEvaluator(new FloatEvaluator());
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
objectAnimator.start();
檢視源碼,可知ObjectAnimator繼承自ValueAnimator;animate()快捷方式是得到ViewPropertyAnimator,而ViewPropertyAnimator内部其實使用的也是ValueAnimator。
産生動畫效果可簡單了解為:1. 值變; 2. UI更新。ValueAnimator扮演的就是屬性值變更和通知的角色,ObjectAnimator,ViewPropertyAnimator則是在他上又一次的封裝。
二.start之後
start()最終會調用如下代碼,由注釋和源碼可見器主要作用是完成一些參數和标志的初始化,同時注冊監聽,來實作不斷的變化回調。
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
* to true if called from the reverse() method.
*
* <p>The animation started by calling this method will be run on the thread that called
* this method. This thread should have a Looper on it (a runtime exception will be thrown if
* this is not the case). Also, if the animation will animate
* properties of objects in the view hierarchy, then the calling thread should be the UI
* thread for that view hierarchy.</p>
*
* @param playBackwards Whether the ValueAnimator should start playing in reverse.
*/
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);
}
}
}
................
................
//層層調用,往下注冊
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
//AnimationFrameCallback
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
................
................
AnimationHandler.java
/**
* Register to get a callback on the next frame after the delay.
*/
//層層調用,往下注冊
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
//進行注冊,關注mFrameCallback裡面的邏輯,會根據動畫執行狀态再次post,來達到不斷循環的目的
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
//上面的 getProvider()得到的是一個MyFrameCallbackProvider對象。通過postFrameCallback,最終執行到mChoreographer.postFrameCallback(callback);注冊到mChoreographer裡
/**
* 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);
}
}
調用Choreographer的postFrameCallback注冊後,會在下一幀重新整理時回調,由注釋可知回調執行一次之後便被清掉了,是以,要想不斷地循環就需要每次收到回調之後再進行注冊
/**
* Posts a frame callback to run on the next frame.
* <p>
* The callback runs once then is automatically removed.
* </p>
*
* @param callback The frame callback to run during the next frame.
*
* @see #postFrameCallbackDelayed
* @see #removeFrameCallback
*/
public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
我們注冊的回調mFrameCallback,裡進行了再次post
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
三.屬性變化監聽
數值變更流程:
View屬性設定流程:
上文分析了整個屬性動畫達到不斷循環,随着時間不斷調整動畫“進度”的整體流程。這一部分則是概述一下,動畫屬性值改變之後是怎麼設定到View上的。
1. 對于我們自己直接使用ValueAnimator實作的動畫,隻是提供數值的變化,通過onAnimationUpdate接口回調給我們,我們在這裡自己把實時變化的屬性值設定到想要設定的View上即可。
2. ObjectAnimator是Android提供的對ValueAnimator的進一步封裝,來簡化我們的開發。這裡則主要分析它的實作原理。
A.建立ObjectAnimator對象時記錄對應的target和屬性名
/**
* Constructs and returns an ObjectAnimator that animates between float values. A single
* value implies that that value is the one being animated to, in which case the start value
* will be derived from the property being animated and the target object when {@link #start()}
* is called for the first time. Two values imply starting and ending values. More than two
* values imply a starting value, values to animate through along the way, and an ending value
* (these values will be distributed evenly across the duration of the animation).
*
* @param target The object whose property is to be animated.
* @param property The property being animated.
* @param values A set of values that the animation will animate between over time.
* @return An ObjectAnimator object that is set up to animate between the given values.
*/
public static <T> ObjectAnimator ofFloat(T target, Property<T, Float> property,
float... values) {
ObjectAnimator anim = new ObjectAnimator(target, property);
anim.setFloatValues(values);
return anim;
}
.......
/**
* Private utility constructor that initializes the target object and property being animated.
*
* @param target The object whose property is to be animated.
* @param property The property being animated.
*/
private <T> ObjectAnimator(T target, Property<T, ?> property) {
setTarget(target);
setProperty(property);
}
B.在動畫漸進過程,進行通知回調,回調中設定View屬性, 接口回調的鍊路上文已記錄,這裡從回調之後開始,即回調doAnimationFrame(...)後的處理邏輯。大緻時序圖如下。
其中,setAnimatedValue(...)方法便完成了屬性設定,這裡是通過反射設定屬性到target,也即要設定屬性動畫的View對象。(此處以FloatPropertyValuesHolder為例,其他類似)
/**
* 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
*/
@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 {
mTmpValueArray[0] = mFloatAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
上面流程省略了反射方法存儲記錄相關流程,這一部分主要是伴随着動畫建立,參數初始化層層傳遞,具體實作都在PropertyValuesHolder中,包括屬性方法是否存在,set/get方法和屬性拼接,大小寫轉換等,此處不做展開。
插值器和估值器何時起作用的?
接上文時序圖,可以看到在更新view屬性是,有一步是要執行animateValue(...);插值器和估值器就是在這裡使用的
/**
* This method is called with the elapsed fraction of the animation during every
* animation frame. This function turns the elapsed fraction into an interpolated fraction
* and then into an animated value (from the evaluator. The function is called mostly during
* animation updates, but it is also called when the <code>end()</code>
* function is called, to set the final value on the property.
*
* <p>Overrides of this method must call the superclass to perform the calculation
* of the animated value.</p>
*
* @param fraction The elapsed fraction of the animation.
*/
@CallSuper
void animateValue(float fraction) {
//使用插值器getInterpolation
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//跳轉到PropertyValuesHolder,針對ofFloat(...)方法,跳轉到的是其子類FloatPropertyValuesHolder
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
.....
//FloatPropertyValuesHolder
@Override
void calculateValue(float fraction) {
//進一步會跳轉到KeyFrame,針對float屬性跳轉到的是實作類FloatKeyframeSet.java
mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}
.....
//FloatKeyframeSet.java
@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();
}
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();
}