每日一問:到底爲何屬性動畫後 View 在新位置還能響應事件

在 Android 開發中,咱們不免會使用動畫來處理各類各樣的動畫效果,以知足 UI 的高逼格設計。對於比較複雜的動畫效果,咱們一般會採用著名的開源庫:lottie-android,或許你會對 lottie 的原理充滿好奇,但這並不在咱們這篇文章的討論範圍,感興趣的自行 Google 吧~java

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

我一度在論壇上看到人使用了 TranslateAnimation 對控件作了移動操做,而後發如今 View 的新位置點擊並無響應本身的點擊事件,反卻是以前的位置可以響應。實際上,補間動畫僅僅是對 View 在視覺效果上作了移動、縮放、旋轉和淡入淡出的效果,其實並無真正改變 View 的屬性。但咱們大多數狀況下確定但願 View 在通過動效後響應觸摸事件的位置和視覺效果相同,因此在 Android 3.0 以後引入了屬性動畫,完全解決了這個難題。android

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

對於屬性動畫,你能夠用下面的兩種方式。github

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

但用補間動畫,而且你想達到一樣的效果的話。ide

val anim = TranslateAnimation(0f, 500f, 0f, 0f)
anim.duration = 1000
anim.fillAfter = true    // 設置保留動畫後的狀態
tv1.startAnimation(anim)

屬性動畫的使用注意點

對於屬性動畫來講,尤爲須要注意的是操做的屬性須要有 set 和 get 方法,否則你的 ObjectAnimator 操做就不會生效。好比水平平移,咱們知道,View 的 translationX 屬性設置方法接受的是 float 值,因此你把上面的操做編寫爲 ofInt 就不會生效,好比:動畫

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

對於咱們須要用到但又沒有寫好的屬性,好比咱們自定義一個進度條 View,咱們須要實時展現進度,這時候咱們就能夠本身定義一個屬性,並讓它支持 set 和 get,那麼在外面就能夠對這個自定義的 View 作屬性動畫操做了。this

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

屬性動畫

屬性動畫的工做原理很簡單,其實就是在必定的時間間隔內,經過不斷地對值進行改變,並不斷將該值賦給對象的屬性,從而實現該對象在屬性上的動畫效果。spa

這個屬性能夠是任意對象的屬性。.net

從上述工做原理能夠看出屬性動畫有兩個很是重要的類:ValueAnimator 類 & ObjectAnimator 類,兩者的區別在於:
ValueAnimator 類是先改變值,而後 手動賦值 給對象的屬性從而實現動畫;是 間接 對對象屬性進行操做;而 ValueAnimator 類本質上是一種 改變值 的操做機制。設計

ObjectAnimator 類是先改變值,而後 自動賦值 給對象的屬性從而實現動畫;是 直接 對對象屬性進行操做;能夠理解爲: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() 方法,很明顯,補間動畫在執行的時候,直接致使了 View 執行 onDraw() 方法。總的來講,補間動畫的核心本質就是在必定的持續時間內,不斷改變 Matrix 變換,而且不斷刷新的過程。

爲何屬性動畫移動一個 View 後,目標位置還能夠響應觸摸事件呢?

這個問題來自 wanandroid,在此前,我一直認爲既然 View 的屬性獲得了改變,那麼通過屬性動畫後的控件應該全部屬性都等同於直接設置在動畫後的位置的控件。

看完「陳小緣」的回答後,我忽然纔想到,雖然 View 作了屬性上的改變,但其實並無更改 Viewleftrighttopbottom 這些屬性,而這些屬性偏偏決定了 ViewGroup 的觸摸區域判斷。

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

那麼,假定咱們的 View 通過了上面的平移操做後,爲何點擊新的位置可以響應到這個點擊事件呢?

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

咱們知道,在 ViewGroup 沒有重寫 onInterceptTouchEvent() 方法進行事件攔截的時候,咱們必定會經過其 dispatchTouchEvent() 方法進行事件分發,而決定咱們哪個子 View 響應咱們的觸摸事件的條件又是 咱們手指的位置必須在這個子 View 的邊界範圍內,也就是 leftrighttopbottom 這四個屬性造成的矩形區域。

那麼,若是咱們的 View 已經進行了屬性動畫後,如今手指響應的觸摸位置區域確定不是 View 本身的leftrighttopbottom 這四個屬性造成的區域了,但這個 View 卻神奇的響應了咱們的點擊事件。

/**
 * 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 是否應用過位移、縮放、旋轉之類的屬性動畫。若是應用過的話,那還會調用子 ViewgetInverseMatrix() 作「反平移」操做,而後再去判斷處理後的觸摸點是否在子 View 的邊界範圍內。

感嘆,今天又發現了一些很是通用卻被咱們忽略掉的東西,不得不說,鴻洋的 wanandroid 帶給了咱們不少東西,更加驚歎的是「陳小緣」同窗的 View 相關功底確實很強,這也難怪,他能寫出如何有逼格的自定義 View 了。

View 相關的很是渴望瞭解的能夠到小緣的博客去一探究竟。 https://me.csdn.net/u011387817

相關文章
相關標籤/搜索