事件分發和處理

稀飯_發表於2018-07-26

一些問題

什麼是事件?

使用者觸控螢幕的位置等資料封裝成物件MotionEvent,這個物件就是事件

什麼是事件分發?

MotionEvent物件傳遞的過程就叫事件分發

MotionEvent都有可能在哪些物件之間傳遞?

AndroidUI介面由ActivityViewGroupView 及其派生類組成,所以MotionEvent在Activity,ViewGroup,View之間傳遞。

事件分發既然是一個過程,就會有順序,那麼MotionEvent傳遞的順序是什麼?

從外向內分發順序為:Activity -> ViewGroup -> View

通過上邊一些問題和答案,應該對事件分發學習有一個方向:就是對MotionEvent傳遞的過程的一個分析

  • ActivityMotionEvent傳遞過程
  • ViewGroupMotionEvent傳遞過程
  • ViewMotionEvent傳遞過程

只要把上邊三個傳遞過程原始碼捋順,就算是掌握了事件分發。

MotionEvent傳遞過程由哪些方法協作完成?

dispatchTouchEvent() :事件分發

onInterceptTouchEvent():事件攔截

onTouchEvent():事件處理

他們之間的邏輯關係我們可以用一個虛擬碼去表示:

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent(ev);
    } else {
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}複製程式碼


那麼我們首先去看Activity中的dispatchTouchEvent方法

    public boolean dispatchTouchEvent(MotionEvent ev) {
        ...

        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }複製程式碼

MotionEvent首先傳遞給window中的superDispatchTouchEvent方法中,  如果裡邊邏輯處理了MotionEvent,那麼直接返回true  即 Activity中不用處理MotionEvent(就是不會呼叫onTouchEvent方法),返回false則事件交給Activity的onTouchEvent去處理。

我們繼續window中去看這個superDispatchTouchEvent方法邏輯,window類是個抽象類,它的實現類是PhoneWindow,那麼我們就去找PhoneWindow中superDispatchTouchEvent方法。

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}複製程式碼

這裡邊呼叫了DecorView的superDispatchTouchEvent方法,我們繼續去到DecorView中

public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}複製程式碼

這裡邊又呼叫了父類的事件分發的方法,而DecorView繼承FrameLayout,我們繼續跟蹤,FrameLayout中沒有重寫dispatchTouchEvent方法,我們繼續找父類,到了ViewGroup中的dispatchTouchEvent方法。

到此,我們就把Activity中的MotionEvent傳遞到ViewGroup中。並且根據返回值來判斷是否需要傳遞給Activity中的onTouchEvent方法中。


我們繼續看ViewGroup中的dispatchTouchEvent原始碼:

  public boolean dispatchTouchEvent(MotionEvent ev) {
        //獲取手勢動作
        final int action = ev.getAction();
        final float xf = ev.getX();
        final float yf = ev.getY();
        final float scrolledXFloat = xf + mScrollX;
        final float scrolledYFloat = yf + mScrollY;
        final Rect frame = mTempRect;
        //是否進行攔截
        boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

        //ViewGroup中ACTION_DOWN事件處理;
        if (action == MotionEvent.ACTION_DOWN) {
            //如果mMotionTarget儲存了View,因為是從新開始的DOWN,所以清空;
            if (mMotionTarget != null) {
                mMotionTarget = null;
            }
            //是否禁用攔截事件功能,如果禁用了攔截功能(不攔截)或者onInterceptTouchEvent()返回false,說明不攔截事件。
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {
                ev.setAction(MotionEvent.ACTION_DOWN);
                final int scrolledXInt = (int) scrolledXFloat;
                final int scrolledYInt = (int) scrolledYFloat;
                final View[] children = mChildren;
                final int count = mChildrenCount;
                //迴圈每一個子View;
                for (int i = count - 1; i >= 0; i--) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                            || child.getAnimation() != null) {
                        child.getHitRect(frame);
                        //判斷當前子View是否包含點選區域;
                        if (frame.contains(scrolledXInt, scrolledYInt)) {
                            final float xc = scrolledXFloat - child.mLeft;
                            final float yc = scrolledYFloat - child.mTop;
                            ev.setLocation(xc, yc);
                            child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                            //呼叫子View的dispatchTouchEvent() ,向子事件分發事件;
                            //1.如果返回true,說明子類或者子類的某個子類中消費了事件,將這個child記錄到mMotionTarget中,
                            // 每一個ViewGroup都記錄了消費了這個事件的子View,像鏈式,最後當前ViewGroup返回true,
                            // 返回給呼叫這個ViewGroup的外層ViewGroup,一直到Activity中的dispatchTouchEvent()中,最後Activity中也返回true;
                            //2.如果返回false,說明子類或者子類的子類中沒有消費這個事件,所以mMotionTarget不會被賦值,會繼續向下執行程式碼;
                            if (child.dispatchTouchEvent(ev)) {
                                mMotionTarget = child;
                                return true;
                            }
                        }
                    }
                }
            }
        }
        boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
                (action == MotionEvent.ACTION_CANCEL);
        if (isUpOrCancel) {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
        final View target = mMotionTarget;
        //ACTION_DOWN事件時,被攔截,或者子類沒有消費ACTION_DOWN,呼叫自己的onTouchEvent()執行onTouch、onClick()方法等;
        if (target == null) {
            ev.setLocation(xf, yf);
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
                ev.setAction(MotionEvent.ACTION_CANCEL);
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            }
            //呼叫super方法即呼叫到View中的dispatchTouchEvent()方法,還是呼叫的當前View,
            //在View中會呼叫自己的onTouchEvent()執行onTouch、onClick()方法等處理;
            //這裡兩種情況:1.自身沒有處理返回false;2.自身處理了返回true,本身處理了,不會儲存到mMotionTarget中,而它的外層ViewGroup會儲存他;
            //true/false都會一直返回到Activity中。
            return super.dispatchTouchEvent(ev);
        }
//===============================以上完成了事件的分發=======================================================

        //在ACTION_UP或者MOVE時,子類中消費了ACTION_DOWN事件,沒有禁用攔截功能(可以攔截),並且攔截事件;
        // 無論 target 是否為 null ,ACTION_DOWN事件的處理都不能走到這裡,在之下都是ACTION_MOVE和ACTION_UP的邏輯
        // 如果執行到這裡,說明有響應ACTION_DOWN事件的view物件,這就看我們是否被允許攔截和要不要攔截了
        // 如果允許攔截並且攔截了ACTION_MOVE和ACTION_UP事件,則將ACTION_CANCEL事件分發給target
        // 然後直接返回true,表示已經響應了該次事件,這時當下個事件來臨時候,就是攔截者去處理事件
        if (!disallowIntercept && onInterceptTouchEvent(ev)) {
            final float xc = scrolledXFloat - (float) target.mLeft;
            final float yc = scrolledYFloat - (float) target.mTop;
            mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            ev.setAction(MotionEvent.ACTION_CANCEL);
            ev.setLocation(xc, yc);
            //將子類事件重置為ACTION_CANCEL;
            if (!target.dispatchTouchEvent(ev)) {
            }
            mMotionTarget = null;
            return true;
        }
        if (isUpOrCancel) {
            mMotionTarget = null;
        }
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        ev.setLocation(xc, yc);
        if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
            ev.setAction(MotionEvent.ACTION_CANCEL);
            target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
            mMotionTarget = null;
        }
        // 如果沒有攔截ACTION_MOVE和ACTION_UP事件,則直接派發給target
        return target.dispatchTouchEvent(ev);
    }複製程式碼

事件分發和處理

下邊看一下在Viewgroup的dispatchTouchEvent方法中繼續向下分發事件程式碼if(child.dispatchTouchEvent(ev))執行後如果child也是一個ViewGroup子類那麼跟上邊的邏輯是一樣的,那麼如果child是一個View的子類,比如Button、TextView、ImageView等時,是如何分發事件的,即事件在View類中的傳遞流程,接著看一下原始碼:

//View中的dispatchTouchEvent;
public boolean dispatchTouchEvent(MotionEvent event) {
    if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
}複製程式碼

在這裡可以看到首先判斷我們有沒有給當前View設定OnTouchListener事件,並且當前View是enabled狀態的,如果前兩個條件都滿足,則會呼叫我們OnTouchListener中的onTouch()方法,如果上邊三個條件都為true,則直接返回true,表明當前View消費當前事件,如果我們在onTouch()方法中返回false,那麼就會跳過判斷執行自己的onTouchEvent()方法,這裡先提前說一下,呼叫onTouchEvent()方法也有繼續判斷當前View是否消費當前事件的作用,在其內部會根據我們點選在控制元件上停留時間判斷是否執行OnClick,OnLongClick事件。





相關文章