每日一問:談?wù)剬傩詣赢嫼脱a間動畫的原理及區(qū)別

在 Android 開發(fā)中,我們難免會使用動畫來處理各種各樣的動畫效果,以滿足 UI 的高逼格設(shè)計。對于比較復(fù)雜的動畫效果,我們通常會采用著名的開源庫:lottie-android,或許你會對 lottie 的原理充滿好奇,但這并不在我們這篇文章的討論范圍,感興趣的自行 Google 吧~

屬性動畫和補間動畫的基本編寫方式

我一度在論壇上看到人使用了 TranslateAnimation 對控件做了移動操作,然后發(fā)現(xiàn)在 View 的新位置點擊并沒有響應(yīng)自己的點擊事件,反倒是之前的位置能夠響應(yīng)。實際上,補間動畫僅僅是對 View 在視覺效果上做了移動、縮放、旋轉(zhuǎn)和淡入淡出的效果,其實并沒有真正改變 View 的屬性。但我們大多數(shù)情況下肯定希望 View 在經(jīng)過動效后響應(yīng)觸摸事件的位置和視覺效果相同,所以在 Android 3.0 之后引入了屬性動畫,徹底解決了這個難題。

可能還有一些小伙伴不明白怎樣的代碼是屬性動畫,怎樣的代碼是補間動畫。下面針對 View 向右平移 500 px 做一下簡單的演示。

對于屬性動畫,你可以用下面的兩種方式。

ObjectAnimator.ofFloat(tv1, "translationX", 0f, 500f)
                    .setDuration(1000)
                    .start()
// 或者像這樣
tv1.animate().setDuration(1000).translationX(500f)

但用補間動畫,并且你想達到同樣的效果的話。

val anim = TranslateAnimation(0f, 500f, 0f, 0f)
anim.duration = 1000
anim.fillAfter = true    // 設(shè)置保留動畫后的狀態(tài)
tv1.startAnimation(anim)

屬性動畫的使用注意點

對于屬性動畫來說,尤其需要注意的是操作的屬性需要有 set 和 get 方法,不然你的 ObjectAnimator 操作就不會生效。比如水平平移,我們知道,View 的 translationX 屬性設(shè)置方法接受的是 float 值,所以你把上面的操作編寫為 ofInt 就不會生效,比如:

ObjectAnimator.ofInt(tv1, "translationX", 0, 500)
                    .setDuration(1000)
                    .start()

對于我們需要用到但又沒有寫好的屬性,比如我們自定義一個進度條 View,我們需要實時展示進度,這時候我們就可以自己定義一個屬性,并讓它支持 set 和 get,那么在外面就可以對這個自定義的 View 做屬性動畫操作了。

屬性動畫和補間動畫工作原理

屬性動畫

屬性動畫的工作原理很簡單,其實就是在一定的時間間隔內(nèi),通過不斷地對值進行改變,并不斷將該值賦給對象的屬性,從而實現(xiàn)該對象在屬性上的動畫效果。

這個屬性可以是任意對象的屬性。

從上述工作原理可以看出屬性動畫有兩個非常重要的類:ValueAnimator 類 & ObjectAnimator 類,二者的區(qū)別在于:
ValueAnimator 類是先改變值,然后 手動賦值 給對象的屬性從而實現(xiàn)動畫;是 間接 對對象屬性進行操作;而 ValueAnimator 類本質(zhì)上是一種 改變值 的操作機制。

ObjectAnimator 類是先改變值,然后 自動賦值 給對象的屬性從而實現(xiàn)動畫;是 直接 對對象屬性進行操作;可以理解為:ObjectAnimator 更加智能、自動化程度更高。

補間動畫

而對于補間動畫,我們不妨跟進源碼,看看到底做了什么操作。

/**
 * Start the specified animation now.
 *
 * @param animation the animation to start now
 */
public void startAnimation(Animation animation) {
    animation.setStartTime(Animation.START_ON_FIRST_FRAME);
    setAnimation(animation);
    invalidateParentCaches();
    invalidate(true);
}

看到了非常明顯 invalidate() 方法,很明顯,補間動畫在執(zhí)行的時候,直接導(dǎo)致了 View 執(zhí)行 onDraw() 方法??偟膩碚f,補間動畫的核心本質(zhì)就是在一定的持續(xù)時間內(nèi),不斷改變 Matrix 變換,并且不斷刷新的過程。

為什么屬性動畫移動一個 View 后,目標(biāo)位置還可以響應(yīng)觸摸事件呢?

這個問題來自 wanandroid,在此前,我一直認(rèn)為既然 View 的屬性得到了改變,那么經(jīng)過屬性動畫后的控件應(yīng)該所有屬性都等同于直接設(shè)置在動畫后的位置的控件。

看完「陳小緣」的回答后,我突然才想到,雖然 View 做了屬性上的改變,但其實并沒有更改 Viewleft、right、top、bottom 這些屬性,而這些屬性恰恰決定了 ViewGroup 的觸摸區(qū)域判斷。

tv1.animate().setDuration(1000).translationX(500f)

那么,假定我們的 View 經(jīng)過了上面的平移操作后,為什么點擊新的位置能夠響應(yīng)到這個點擊事件呢?

看了「陳小緣」的回答,我順便深入了一波源碼,想想必須在這分享給大家。

我們知道,在 ViewGroup 沒有重寫 onInterceptTouchEvent() 方法進行事件攔截的時候,我們一定會通過其 dispatchTouchEvent() 方法進行事件分發(fā),而決定我們哪一個子 View 響應(yīng)我們的觸摸事件的條件又是 我們手指的位置必須在這個子 View 的邊界范圍內(nèi),也就是 left、righttop、bottom 這四個屬性形成的矩形區(qū)域。

那么,如果我們的 View 已經(jīng)進行了屬性動畫后,現(xiàn)在手指響應(yīng)的觸摸位置區(qū)域肯定不是 View 自己的leftrighttopbottom 這四個屬性形成的區(qū)域了,但這個 View 卻神奇的響應(yīng)了我們的點擊事件。

/**
 * Returns a MotionEvent that's been transformed into the child's local coordinates.
 *
 * It's the responsibility of the caller to recycle it once they're finished with it.
 * @param event The event to transform.
 * @param child The view whose coordinate space is to be used.
 * @return A copy of the the given MotionEvent, transformed into the given View's coordinate
 *         space.
 */
private MotionEvent getTransformedMotionEvent(MotionEvent event, View child) {
    final float offsetX = mScrollX - child.mLeft;
    final float offsetY = mScrollY - child.mTop;
    final MotionEvent transformedEvent = MotionEvent.obtain(event);
    transformedEvent.offsetLocation(offsetX, offsetY);
    if (!child.hasIdentityMatrix()) {
        transformedEvent.transform(child.getInverseMatrix());
    }
    return transformedEvent;
}

/**
 * Returns true if the transform matrix is the identity matrix.
 * Recomputes the matrix if necessary.
 *
 * @return True if the transform matrix is the identity matrix, false otherwise.
 */
final boolean hasIdentityMatrix() {
    return mRenderNode.hasIdentityMatrix();
}

/**
 * Utility method to retrieve the inverse of the current mMatrix property.
 * We cache the matrix to avoid recalculating it when transform properties
 * have not changed.
 *
 * @return The inverse of the current matrix of this view.
 * @hide
 */
public final Matrix getInverseMatrix() {
    ensureTransformationInfo();
    if (mTransformationInfo.mInverseMatrix == null) {
        mTransformationInfo.mInverseMatrix = new Matrix();
    }
    final Matrix matrix = mTransformationInfo.mInverseMatrix;
    mRenderNode.getInverseMatrix(matrix);
    return matrix;
}   

原來,ViewGroupgetTransformedMotionEvent() 方法中會通過子 ViewhasIdentityMatrix() 方法來判斷子 View 是否應(yīng)用過位移、縮放、旋轉(zhuǎn)之類的屬性動畫。如果應(yīng)用過的話,那還會調(diào)用子 ViewgetInverseMatrix() 做「反平移」操作,然后再去判斷處理后的觸摸點是否在子 View 的邊界范圍內(nèi)。

補間動畫和屬性動畫改變的是同一個 Matrix 么?

負(fù)責(zé)任的說:肯定不是。

屬性動畫所影響的 Matrix,是在 ViewmRenderNode 中的 stagingProperties 里面的,這個 Matrix 在每個 View 之間都是獨立的,所以可以各自保存不同的變換狀態(tài)。

而補間動畫所操作的 Matrix,其實是借用了它父容器的一個叫 mChildTransformation 的屬性 ( 里面有 Matrix ),通過 getChildTransformation 獲得。
也就是說,一個 ViewGroup 中,無論它有幾個子 View 都好,在這些子 View 播放補間動畫的時候,都是共用同一個 Transformation 對象的(也就是共用一個 Matrix ),這個對象放在 ViewGroup 里面。

有同學(xué)可能會問:共用?不可能吧,那為什么可以同時播放好幾個動畫,而互相不受影響呢?
是的,在補間動畫更新每一幀的時候,父容器的 mChildTransformation 里面的 Matrix,都會被 reset() 。

每次重置 Matrix 而不受影響的原因:
是因為這些補間動畫,都是基于當(dāng)前播放進度,來計算出絕對的動畫值并應(yīng)用的,保存舊動畫值是沒有意義的。
就拿位移動畫 TranslateAnimation 來說,比如它要向右移動 500,當(dāng)前的播放進度是 50%,那就是已經(jīng)向右移動了 250,在 View 更新幀的時候,就會把這個向右移動了250的 Matrix 應(yīng)用到 Canvas 上,當(dāng)下次更新幀時,比如進度是 60%,那計算出來的偏移量就是 300,這時候,已經(jīng)不需要上一次的舊值 250 了,就算 Matrix 在應(yīng)用前被重置了,也不影響最后的效果。

感嘆,今天又發(fā)現(xiàn)了一些非常通用卻被我們忽略掉的東西,不得不說,鴻洋的 wanandroid 帶給了我們很多東西,更加驚嘆的是「陳小緣」同學(xué)的 View 相關(guān)功底確實很強,這也難怪,他能寫出如何有逼格的自定義 View 了。

View 相關(guān)的非??释私獾目梢缘叫【壍牟┛腿ヒ惶骄烤埂?https://me.csdn.net/u011387817

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 【Android 動畫】 動畫分類補間動畫(Tween動畫)幀動畫(Frame 動畫)屬性動畫(Property ...
    Rtia閱讀 6,437評論 1 38
  • 1 背景 不能只分析源碼呀,分析的同時也要整理歸納基礎(chǔ)知識,剛好有人微博私信讓全面說說Android的動畫,所以今...
    未聞椛洺閱讀 2,861評論 0 10
  • 在手機上去實現(xiàn)一些動畫效果算是件比較炫酷的事情,因此Android系統(tǒng)在一開始的時候就給我們提供了兩種實現(xiàn)動畫效果...
    Ten_Minutes閱讀 3,934評論 3 11
  • 概述 在Android開發(fā)的過程中,View的變化是很常見的,如果View變化的過程沒有動畫來過渡而是瞬間完成,會...
    小蕓論閱讀 39,162評論 18 134
  • 自從看了阿爾薩斯的故事,真有種久久不能忘懷的感覺。是什么讓這樣一個人物如此吸引人呢? 身為洛丹倫的王子,白銀之手的...
    三點水滴閱讀 877評論 0 1

友情鏈接更多精彩內(nèi)容