概述
android動畫經常會碰到卡頓,或者阻塞主進程之類的問題。
爲了排查此類問題,不得不對動畫原理了解一二,於是作此文。
此文圍繞兩個主線問題展開:
- ui更新的頻率是如何控制的?
比如,1秒內會更新多少次? - 每次更新UI的時候所帶的update的value是如何控制的?
比如,現在有個0到100的動畫,在執行到30%的時候,value是多少?(可能非線性變化)
對動畫還是不太瞭解的讀者可以看下筆者之前的文章:
Android動畫總結 (valueAnimator、objectAnimator、AnimatorSet、PropertyValuesHolder、Interpolator)
ValueAnimator源碼
動畫平時使用的API較多,但是最終的原理都是一樣的,本文就選擇從ValueAnimator的源碼入手分析這兩個問題。
ui更新的頻率
Demo代碼如下:
ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1000);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override public void onAnimationUpdate(ValueAnimator animation) {
Log.d("test", animation.getAnimatedValue() + "");
}
});
valueAnimator.start();
直接在onAnimationUpdate方法中打斷點,找到調用任務棧:
最終是進入Choreographer的doFrame方法。
Choreographer的doFrame方法是在繪製每一幀的時候調用的,因此動畫的更新頻率與幀的更新頻率一致。至於幀的更新頻率是多少就看具體手機了。(每秒執行doFrame的次數其實就是fps)
update的value如何控制?
首先看下getAnimatedValue源碼:
PropertyValuesHolder[] mValues;
public Object getAnimatedValue() {
if (mValues != null && mValues.length > 0) {
return mValues[0].getAnimatedValue();
}
return null;
}
由此我們可以知道,取的邏輯沒有什麼特殊的地方,其值在更新的時候早就設置好了,因此我們要找下設置的邏輯。
我們再使用ValueAnimation的時候,真正觸發動畫的邏輯其實是start()方法,因此我們從start()方法源碼入手。經過如下調用,可以進入到設置value的地方:
- ValueAnimation.start()
- ValueAnimation.start(boolean playBackwards)
- ValueAnimation.setCurrentFraction(float fraction)
- ValueAnimation.animateValue(float fraction)
PropertyValuesHolder[] mValues;
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);
}
}
}
fraction可以認爲是當前的值的百分比,比如我當前的值的範圍是0~100,當前是120,那麼fraction就是1.2f,如果當前是50,那麼fraction就是0.5f。
此處可以看到直接是將fraction傳入到PropertyValuesHolder.calculateValue()方法來計算的value。
Keyframes mKeyframes = null;
void calculateValue(float fraction) {
Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
此處要了解Keyframes,可以從ValueAnimator.ofInt(int… values)從上至下看,也可以直接看PropertyValuesHolder中Keyframes的賦值來推導。
此處我們採用大家更熟悉的方式來,從ValueAnimator.ofInt(int… values)從上至下看:
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 setIntValues(int... values) {
mValueType = int.class;
mKeyframes = KeyframeSet.ofInt(values);
}
public static KeyframeSet ofInt(int... values) {
int numKeyframes = values.length;
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
} else {
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
}
}
return new IntKeyframeSet(keyframes);
}
知道ValueAnimator.ofInt(int… values)其實就對應IntKeyframeSet之後,我們就可以以IntKeyframeSet爲例來看下getValue的代碼:
@Override
public Object getValue(float fraction) {
return getIntValue(fraction);
}
public int getIntValue(float fraction) {
if (fraction <= 0f) {
final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
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 + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
intValue();
} else if (fraction >= 1f) {
final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2);
final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
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 + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
}
IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {
IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
// Apply interpolator on the proportional duration.
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
return mEvaluator == null ?
prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
intValue();
}
prevKeyframe = nextKeyframe;
}
// shouldn't get here
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
}
一般情況下,如果是線性插值器的話,fraction其實就只代表進度,那麼fraction是肯定>0且<1的,我們要看的代碼其實只有這一段:
IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {
IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
int prevValue = prevKeyframe.getIntValue();
int nextValue = nextKeyframe.getIntValue();
// Apply interpolator on the proportional duration.
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
return mEvaluator == null ?
prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
intValue();
}
prevKeyframe = nextKeyframe;
此時用一個例子來看就非常簡單:
ValueAnimator.ofInt(0,-20,120,40);
此時,這幾個值對應的fraction分別是:(這個可以通過上面KeyframeSet.ofInt()的源碼得到)
- 0:0
- -20:1/3
- 120: 2/3
- 40: 1
因此,如果此時fraction爲0.5的話,1/3<0.5<2/3 那麼此時nextValue就爲120,prevValue就爲-20。
至於具體的返回值,分別需要由插值器或者mEvaluator來決定。
自定義插值器或者其他一些特殊的情況此處就不增加篇幅了。
有了代碼,邏輯也很清晰,讀者可以自己考慮下。