android 事件分發(fā)

??在android中事件分發(fā)很重要,也是比較難理解的。這里講事件分發(fā)記錄一下方便,方便以后溫故復習。本文我們就android中的view和viewgroup的事件分發(fā)機制進行剖析。

傳遞規(guī)則

??android中事件傳遞從activity 》window》doecorView 》ViewGroup》view。由此看出事件分發(fā)流程是由上而下,由外到內(nèi)。其核心就是ViewGroup和View 兩個層面的事件分發(fā)。事件分發(fā)的核心方法有三個dispatchTouchEvent()分發(fā)事件、onInterceptTouchEvent()是否攔截事件、onTouchEvent()處理事件。其中還穿插著onTouchListener的onTouch()事件處理OnClickListener的onClick()點擊事件響應。,其中view因為沒有子View所以不涉及攔截事件,所以沒有onInterceptTouchEvent()方法;

事件分發(fā)流程
    //事件分發(fā)偽代碼
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean result = false;
        if (onInterceptTouchEvent(ev)) {
            if (onTouchListener != null) {
                result = onTouchListener.onTouch(view, ev);
            }
            if (!result) {
                result = onTouchEvent(ev);
            }
        } else {
            result = getChildAt(0).dispatchTouchEvent(ev);
        }
        return result;
    }
    //onTouchEvent偽代碼展示onClickListener觸發(fā)時機
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                if (onClickListener != null) {
                    onClickListener.onClick(view);
                    return true;
                }
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }

通過以上源碼偽代碼我們可以看出在所有的實踐中onTouchListener的優(yōu)先級比較高,點擊事件的優(yōu)先級最低。優(yōu)先級順序為onTouchListener.onTouch() > onTouchEvent() >onClickListener.onClick();下面我們根據(jù)源碼解釋一下以上優(yōu)先級。
??源碼角度分析事件分發(fā)

ViewGroup事件分發(fā)
 public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            // Handle an initial down.初始化一個down事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
               //*******************標記***1*********************
                //ACTION_DOWN是一個事件序列的起點,清空原有標記位
                //清空標記的 mFirstTouchTarget(上一個事件處理子View)和清除FLAG_DISALLOW_INTERCEPT標記
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //*******************標記***2*********************
                //如果是ACTION_DOWN或者子view已經(jīng)處理事件了 正常執(zhí)行事件分發(fā)邏輯。否則直接攔截返回true
                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 {
                intercepted = true;
            }
            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
            //判斷是否取消和攔截事件
            if (!canceled && !intercepted) {
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    //去過濾子控件是否處理事件
                    //*******************標記***3*********************
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            //*******************標記***4*********************
                            //如果child能夠接受事件,并且點擊事件在view的坐標區(qū)域內(nèi),交由子view處理,否則繼續(xù)查找其他子view
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //子view分發(fā)處理事件
                            //*******************標記***5*********************
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                              //子view處理事件將子view添加到mFirstTouchTarget中。
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                //*******************標記***6*********************
                //沒有子view處理當前事件,則調(diào)用自身處理點擊事件。這里child參數(shù)為空,則執(zhí)行super.dispatchTouchEvent(event);自身處理觸摸事件
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // 取消分發(fā)事件,排除新添加的TouchTarget和已經(jīng)分發(fā)過得,
                .......
                } 
              //*******************標記***7*********************
            // 當取消事件、抬起事件等清空還原緩存觸摸標記
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }
        return handled;
    }

viewGroup事件分發(fā)流程解讀:

  1. 當一個事件到來如果是ACTION_DOWN事件,會復位之前的標記位(TouchTarget、FLAG_DISALLOW_INTERCEPT等)參照偽代碼中標記1位置。
  2. 判斷如果不是ACTION_DOWN或者touchTarget為空,說明這不是一個新的事件序列或者該序列事件沒有子view處理,這直接攔截事件 intercepted = true;。否則則執(zhí)行onInterceptTouchEvent()事件處理。參照偽代碼中標記2位置。
  3. 如果不攔截事件并且沒有取消,則當子view中查找,子view是否需要事件。參照偽代碼中標記3位置。
  4. 子view判斷如果在事件坐標在view的 坐標區(qū)域內(nèi),則執(zhí)行dispatchTransformedTouchEvent()方法查看子view是否需要事件。參照偽代碼中標記4位置。
  5. dispatchTransformedTouchEvent()方法如果child不為空,那么調(diào)用child.dispatchTouchEvent(event);交由child處理事件,如果child為空調(diào)用super.dispatchTouchEvent(event);事件自己本身處理。
  6. 調(diào)用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)后,如果返回ture說明子view處理事件。那么將child添加到touchTraget中。參照偽代碼中標記5位置。
  7. mFirstTouchTarget 為空即沒有子view消費事件,那么直接調(diào)用dispatchTransformedTouchEvent()此時傳參child為null,那么執(zhí)行super.dispatchTouchEvent(event);事件自己本身處理。參照偽代碼中標記6位置。
  8. 當取消事件、抬起事件等清空還原緩存觸摸標記參照偽代碼中標記5位置。

ViewGroup的onTouchEvent繼承自View的不在單獨分析。onInterceptTouchEvent()一般默認false。其他控件根據(jù)自身需求自己定義,這里不做分析。
下面分析一下View的事件處理源碼:

View事件分發(fā)
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (onFilterTouchEventForSecurity(event)) {
            //如果設置了mOnTouchListener 則優(yōu)先執(zhí)行mOnTouchListener 的onTouch()方法。
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //在執(zhí)行本身的onTouchEvent()方法
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        return result;
    }
//觸摸解析
        public boolean onTouchEvent(MotionEvent event) {
        //是否可點擊
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

        //如果view不可用,直接返回點擊狀態(tài),如果可以點擊返回true,同樣會消耗事件。反之則返回false,不消費事件
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            //如果view不可用,但是同樣會消耗點擊事件
            return clickable;
        }
        //只要是可點擊都會返回true
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    ......
                    //響應點擊事件
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClick();
                    }
                    ......
                    break;
                ......
            }
            return true;
        }
        return false;
    }

view的dispatchTouchEvent()事件分發(fā)方法,因為沒有子view不存在傳遞事件,所以就很簡單。在view的時間分發(fā)中,如果view設置了onTouchListener那么先執(zhí)行onTouchListener.onTouch()方法先處理事件,如果沒有消費事件則執(zhí)行view的onTouchEvent()方法處理事件。
view的onTouchEvent()方法中

  1. 先判斷view是否可點擊(包含:點擊、長按等點擊標記)。
  2. 判斷view是否可用,如果view不可用,直接返回點擊狀態(tài),如果可以點擊返回true,同樣會消耗事件。反之則返回false,不消費事件。
  3. 在分析action中大部分都是在分析view的按壓等狀態(tài)。這里不做詳細描述。我們只看在MotionEvent.ACTION_UP中執(zhí)行了performClick()響應了設置的mOnClickListener的onClick()方法。

事件分發(fā)結(jié)論:

  1. 一個事件序列是指當時手指按下到手指抬起中間的所有事件,也就是由down開始到up結(jié)束中間的所有事件。
  2. 正常情況下,同一個事件序列只能被一個view攔截且消費。
  3. 某個view一旦攔截(dispatchTouchEvent()方法返回true),這個事件序列都將由此view處理,并且就不會在執(zhí)行onInterceptTouchEvent()方法。
  4. 如果一個view攔截了某個事件,如果它不消費ACTION_DOWN事件(onTouchEvent()方法返回false),那么后續(xù)事件不會交給他處理,事件會向上返回交給父控件的onTouchEvent()處理。
  5. 如果view只消費ACTION_DOWN事件,那么其他的時間也不會交給父控件觸發(fā),并且當前view可以收到后續(xù)的事件,最終這些后續(xù)事件會返回給activity處理。
  6. View沒有分發(fā)事件方法,即沒有onInterceptTouchEvent()方法,會直接觸發(fā)onTouchEvent()方法。
  7. 事件分發(fā)是由外向內(nèi)的,有父控件向子控件傳遞,除非父控件攔截事件。子控件可以調(diào)用requestDisallowInterceptTouchEvent()方法請求父控件不要攔截事件。但是ACTION_DOWN除外。
  8. view事件分發(fā)的優(yōu)先級順序為onTouchListener.onTouch() > onTouchEvent() >onClickListener.onClick()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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

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