Android事件分發機制,你瞭解過嗎?

CVTE一面答的很很差的題目之一,特別寫一篇博客反思本身。我記得我當時答的糊里糊塗的,只說了連事件從何時開始響應的也沒說,只說了從子控件開始接受到,若是不消費,或者沒有子控件可以消費時,就向上傳遞,一直傳到根佈局後自動消費。java

所謂的安卓事件是什麼?具體來講的就是點擊和滑動兩個操做;抽象着來講就是下面的表格。函數

MotionEvent/事件類型 具體操做
ACTION_DOWN 點下View
ACTION_UP 擡起View
ACTION_MOVE 滑動View
ACTION_CANCEL 非人爲因素取消

事件序列通常組成:佈局

點擊的事件組成就是:Down --> Uppost

滑動的事件組成就是:Down --> Move --> Move .... --> Upthis


事件分發

  1. 使用到的函數
    • dispatchTouchEvent():用於事件分發
    • onTouchEvent():消費事件
    • onInterceptTouchEvent():判斷是否攔截事件,僅存在於ViewGroup
  2. 分發對象
    • Activity
    • ViewGroup
    • View

Activity的事件分發

public boolean dispatchTouchEvent(MotionEvent ev) {
    // 從判斷語句中能夠得出全部事件的起點就是Down
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        // 實現屏保功能
        onUserInteraction();
    }
    // 向上傳遞至ViewGroup,調用其dispatchTouchEvent
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}
複製代碼

文章中我已經添加了註釋內容,其中getWindow()得到就是一個Window抽象類,根據其子類PhoneWindow咱們能夠很容易得知最後調用的其實就是ViewGroupdispatchTouchEvent()方法spa

/** * 實際上就是判斷事件是不是DOWN事件,event的座標是否在邊界內等 */
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }
    return false;
}
複製代碼

最後就是Activity中的onTouchEvent()方法了,這個模塊乾的事情在註釋中也就很清晰明瞭了。rest


ViewGroup的事件分發

public boolean dispatchTouchEvent(MotionEvent ev) {
    ········
    // 初始化Down事件
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // 丟棄以前手頭上乾的事情,從新開始響應Down事件
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }
    // 檢查是否須要攔截
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        // 這個與運算是用於影響除Down之外的事件的
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
        } else {
            intercepted = false;
        }
    } else {
        // 當前按壓的位置沒有控件,或者當前控件並不可被點擊,直接被ViewGroup攔截
        intercepted = true;
    }
    ········
    /** *這個判斷裏面一樣的仍是判斷響應的事件,而後就是經過一個for循環判斷位置來判斷當前的子控件是否在對應的位置內 * 還有很是重要的一點就是這個循環的判斷仍是倒敘的 */
    if (actionMasked == MotionEvent.ACTION_DOWN
        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
        || actionMasked ==MotionEvent.ACTION_HOVER_MOVE) {
        ········
        if (newTouchTarget == null && childrenCount != 0) {
            ········
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                // 後面的篇幅主要用於判斷當前的控件的各類屬性是不是知足須要的。好比說位置、是否能夠點擊、是否隱藏等一系列信息
                ········ 
            }
   ········
}
複製代碼
public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}
複製代碼

onInterceptTouchEvent(MotionEvent ev)函數可知,默認其實並不會去攔截。因此就通常狀況而言,dispatchTouchEvent()方法是須要去循環遍歷子控件集合去尋找對應的控件的。code

使用一個僞代碼解釋以上的邏輯orm

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;
    if(onInterceptTouchEvent(ev)){
        // 本身攔截,本身消費
        onTouchEvent(ev);
    }else{
        // 不攔截,分發給子View進行消費
        consume = child.dispatchTouchEvent(ev);
    }
    return consume;
}
複製代碼

View事件分發

public boolean dispatchTouchEvent(MotionEvent event) {
        ·····
        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
        
        if (onFilterTouchEventForSecurity(event)) {
            ·····
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return result;
    }
複製代碼

經過以往的實踐,咱們知道只有經過設置了監聽器的View纔可以去監聽事件,那麼在dispatchTouchEvent()方法中也是同樣的,若是View並無被設置監聽器,變量result也不會被賦值成爲true。cdn

從代碼中很容易看出onTouch()方法的優先級大於onTouchEvent()方法。

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();

        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        ·····
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    ·····
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {

                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            removeLongPressCallback();

                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClickInternal();
                                }
                            }
                        }
                        ·····
                    }
                    ·····
                    mIgnoreNextUpEvent = false;
                    break;
                ·····
            }
            return true;
        }
        return false;
    }
複製代碼

onTouchEvent()方法中其實具體幹了一件事情,那就是區別究竟是長按事件仍是點擊事件。

那麼先行判斷的是長按事件仍是點擊事件呢?答案很明顯,在代碼行中removeLongPressCallback();有一個這樣的函數,這就是去除長按事件回調的函數,因此答案就是長按事件是第一個被判斷的事件,而後纔是點擊事件。

判斷這個方法的事件的方法就是經過作出Up動做時的時間和作出Down動做時的時間間隔。若是Down和Up兩個動做之間的時間間隔小於500ms,就是點擊事件。


總結

Android事件分發機制.png
相關文章
相關標籤/搜索