屬性動畫
參考資源:屬性動畫 ValueAnimator 運行原理全解析
上一節我們看過了Animation視圖動畫的原理淺析,瞭解到Animation的原理是通過ViewRootImpl來監聽下一個屏幕刷新信號,然後DecorView對View樹重新繪製並且順帶將動畫繪製出來。
關於屬性動畫有兩個常用類ValueAnimator和它的子類ObjectAnimator,還有view.animate()會得到一個ViewPropertyAnimator對象,需要自己重寫其updateListener做動畫效果。
基本使用
ValueAnimator本身是沒有動畫效果的,需要用戶自定義listener,獲取到計算好的animatedValue進行賦值給view。
val animator1 = ValueAnimator.ofFloat(0f, 1.5f)
animator1.duration = 1000
animator1.repeatCount = 3
animator1.repeatMode = ValueAnimator.REVERSE
animator1.addUpdateListener {
val data = it.animatedValue as Float
testAnimal.scaleX = data
testAnimal.scaleY = data
}
ObjectAnimator繼承於ValueAnimator,提供了對改變View屬性的動畫支持。官網解釋爲需要適合的get/set屬性方法去設置這個值,也就是這個屬性在View類是存在的,如果不存在也可以自己去寫屬性的get/set方法。需要注意的是ObjectAnimator也有ofFloat、ofInt、ofArgb等方法,需要確定屬性的返回值是否一致,而且屬性名要駱駝命名法。
// translationY、scaleX、scaleY、alpha等是View類的基本屬性
val animator1 = ObjectAnimator.ofFloat(testAnimal, "translationY", 0f, -120f)
val animator2 = ObjectAnimator.ofFloat(testAnimal, "scaleX", 0f, -120f)
class View {
@ViewDebug.ExportedProperty(category = "drawing")
public float getTranslationY() {
return mRenderNode.getTranslationY();
}
public void setTranslationY(float translationY) {
//...
}
//scaleX同上
}
進階用法
(1)自定義屬性動畫
View中width和height是沒有set方法的,但是動畫需要改變View的寬度(scale是拉伸View),這時候需要包裝屬性的set方法,或者使用ValueAnimator重寫動畫的updateListener。
// 第一種 包裝屬性
val animator1 = ObjectAnimator.ofInt(ViewWrapper(btn_start2), "width", 120, 420)
animator1.duration = 1000
private inner class ViewWrapper(val target: View) {
fun getWidth(): Int {
return target.layoutParams.width
}
fun setWidth(width: Int) {
target.layoutParams.width = width
target.requestLayout()
}
}
// 第二種 ValueAnimator重寫動畫
// 可以使用估值器計算值的百分比,在之後的動畫原理會講到TypeEvaluator
val animator1 = ValueAnimator.ofInt(120, 420)
animator1.duration = 1000
animator1.addUpdateListener {
val data = it.animatedValue as Int
testAnimal.layoutParams.width = data
testAnimal.requestLayout()
}
(2)AnimatorSet組合動畫
Animator同樣會有組合動畫,並且可對多個View進行動畫,設置動畫的執行順序,更能操控動畫的執行過程。
// Node包裝了動畫的節點,對動畫進行分類成父動畫、子動畫、兄弟動畫
animatorSet = AnimatorSet()
val animator1 = ObjectAnimator.ofInt(ViewWrapper(view2), "width", 120, 420)
val animator3 = ValueAnimator.ofFloat(0f, 1.5f)
animator3.addUpdateListener {
val data = it.animatedValue as Float
view1.scaleX = data
view1.scaleY = data
}
animatorSet.duration = 1000
animatorSet.play(animator1).after(animator3)
/** class AnimatorSet */
// 同時執行的動畫
public Builder with(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addSibling(node);
return this;
}
// 之前執行的動畫
public Builder before(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addChild(node);
return this;
}
// 之後執行的動畫
public Builder after(Animator anim) {
Node node = getNodeForAnimation(anim);
mCurrentNode.addParent(node);
return this;
}
Animator動畫原理解析
瞭解到ObjectAnimator繼承於ValueAnimator,我們直接從ValueAnimator看。
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) { // 保證線程與Looper關聯
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards; //playBackwards傳入參數爲true或false,表示是否動畫反向播放
mSelfPulse = !mSuppressSelfPulseRequested;
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
// 添加Animation回調
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
startAnimation(); // 做一些動畫的初始化工作
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
// 注意這裏傳入的是this,意味着會在ValueAnimator中回調
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
AnimationHandler用於確保動畫使用計算後的同一值,保證動畫的同步執行
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
//記住這裏哈 等下還要回來的
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
/** 註冊callBack以獲取下一幀延時回調
*/
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
// 當前的mAnimationCallbacks爲空的話(第一次執行動畫)
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;
}
/**
* 從這裏可以看到Choreographer類,這個類我們在Animation和View的繪製過程中都可以看到。
* 這個類做了什麼?
* Choregrapher類主要來控制同步處理輸入、動畫、繪製三個UI操作,也就是當屏幕的每一次刷新信號到來時要去處理某
* 些事
* mFrameInfo.markInputHandlingStart();
* doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
* mFrameInfo.markAnimationsStart();
* doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
* mFrameInfo.markPerformTraversalsStart();
* doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
* // 遍歷上述事件完成,提交操作,用於修正動畫的啓動時間
* doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
*/
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
// 發送禎回調通知,以在下一禎時回調
mChoreographer.postFrameCallback(callback);
}
// ...
}
/** 回到上面說的地方,下一幀通知過來了,正式去處理動畫內容
*/
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
// 這裏就是上面所說ValueAnimator添加callback,傳入this
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) { // 判斷動畫是否過期
callback.doAnimationFrame(frameTime); // 調用ValueAnimator的doAnimationFrame回調
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList(); // 動畫執行話,清除callBack列表
}
ValueAnimator類中真正執行動畫的過程,與Animation一致,都是通過估值器、插值器等計算出百分比值等,在動畫的Update中去繪製。
public final boolean doAnimationFrame(long 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) {
//省略代碼 計算動畫進度,保證動畫進度處於0~1之間,如果有設置repeatCount屬性,則這個值是會超過1的,所以需要經過計算轉換爲0~1
animateValue(currentIterationFraction); // 對應Animation的applyTransformation()方法
}
return done;
}
/** 清除Animation的回調,回覆lastFramTime、mStarted、mRunning等屬性值
*/
private void endAnimation() {
if (mAnimationEndRequested) {
return;
}
removeAnimationCallback();
// 省略大法
}
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); // 設置View屬性的地方
}
if (mUpdateListeners != null) { // 如果Animator有添加updateListener方法,則操作地方在這裏
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
關於mValues[i].calculateValue(fraction)的代碼,等有時間再去深究其中代碼,這裏已交代完了Animator的執行流程,下圖是Animator的調用流程圖(手動盜圖)
屬性動畫的優點
- 動畫的執行整體可控制,可以添加listener進行控制動畫過程
- 動畫不限於自有屬性,可自定義屬性進行動畫實現
- 可以改變View的屬性,不僅限於只繪製其位置