1. 程式人生 > >Android中的事件分發機制基本認識

Android中的事件分發機制基本認識

一、基礎認識

Android 中事件的傳遞流程:
Activity--------> Window(PhoneWindow)------>DecroView------->ViewGroup----->View;

二、Activity中事件分發

螢幕發生觸控事Android系統層接受到事件,然後反饋給此時棧頂的App的Acivity,並回調Activity中的dispatchTouchEvent(MotionEvent ev)函式,然後才是事件分發與消費;在Activity中事件分發程式碼十分簡單,原始碼入手:
Activity中dispatchTouchEvent(MotionEvent ev)原始碼:

 public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

根據原始碼先向Windows傳遞事件,如果Window的superDispatchTouchEvent(MotionEvent ev)如果返回true[消費事件],則函式立即返回,否則繼續呼叫Activity的onTouchEvent(MotionEvent ev)處理;

  public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }
  • 在Activity中可以直接是重新onTouchEvent函式實現攔截事件,需要注意的是根據dispatchTouchEvent中原始碼可知,如果需要在保證在Activiy的其他元件中不能攔截事件,否則Activity中dispatchTouchEvent函式就無效了。

三、 Window、DecorView中事件分發

PhonWindow中superDispatchTouchEvent(MotionEvent ev)原始碼;
根據Activity中的 dispatchTouchEvent(MotionEvent ev) 原始碼可知繼續向Window傳遞事件的,在Android中Window是一個抽象類並且只有PhoneWindow一個子類,所以直接看PhonWindow中的superDispatchTouchEvent(MotionEvent ev);

 public boolean superDispatchTouchEvent(MotionEvent event) {
        return this.mDecor.superDispatchTouchEvent(event);
    }

顯然在PhoneWindow中是直接傳遞給DecorView中superDispatchTouchEvent(MotionEvent event)進行了進一步的分發的;繼續跟蹤DecorView中的原始碼:

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

四、 ViewGroup中事件分發

在原始碼分析之前寫理一下流程,然後在根據原始碼分段解析:
ViewGroup中事件分發函式dispatchTouchEvent(MotionEvent ev)的流程虛擬碼:

public boolean dispatchTouchEvent(MotionEvent ev) {
       /* 事件連續性、頁面焦距的 安全性 check  過濾無效事件、清楚無效資料恢復狀態*/
         .......
        /*是否攔截、取消的判斷*/
        if (actionMasked != 0 && this.mFirstTouchTarget == null) {
            intercepted = true;
        } else {
            canceled = (this.mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!canceled) 
                intercepted = this.onInterceptTouchEvent(ev);//攔截呼叫onInterceptTouchEvent函式
        }
        /*如果當前事件沒有被攔擷取消,則繼續向下分發*/
        if (!canceled && !intercepted) {
            /*準備所以是childView,並且過濾出具體需要分發的childView,*/
                 .......
                    //向下分發的目標ChildView 
                     ArrayList<View> preorderedList = this.buildTouchDispatchChildList();
                     .......
                     /*進行向childView分發事件*/
                    for(int i = childrenCount - 1; i >= 0; --i) {
                           /*對childView進行check是否可以接受事件、分發事件之前reset狀態,防止出現事件混亂【down、move、cancel……先後順序混亂】*/
                          .......
                            /*進行向下childView分發 ,dispatchTransformedTouchEvent具體分發函式*/
                            if (this.dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                /*記錄消費事件的目標ChildVewi的時間和在List<ChildVewi>位置*/
                                 .......
                                /*記錄消費事件View的物件和指標*/
                                newTouchTarget = this.addTouchTarget(child, idBitsToAssign);
                                break;
                            }
                        /*如果在childView的List中沒有目標View【在觸控點選的位置沒字View控制元件,只有Layout】*/
                        if (this.mFirstTouchTarget == null) {
                            /* 呼叫ViewGroup的super.dispatchTouchEvent(event),就是呼叫ViewGroup的父類View的dispatchTouchEvent函式,
                             在View的dispatchTouchEvent然後回撥onTouchEvent(event) */
                            handled = this.dispatchTransformedTouchEvent(ev, canceled, (View)null, -1);
                        } else {
                            /*變數、物件的回收處理*/
                        .......
                    }
            }}
            return handled;
    }

在上面的虛擬碼中已經把ViewGroup中的事件分發流程基本搞清楚,主要分為:
1.判斷是否攔截;
2.向ChildView分發事件;
ViewGroup中dispatchTouchEvent完整原始碼:

public boolean dispatchTouchEvent(MotionEvent ev) {
        //驗證事件是否連續
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        //這個變數用於記錄事件是否被處理完
        boolean handled = false;
        //過濾掉一些不合法的事件:當前的View的視窗被遮擋了。
        if (onFilterTouchEventForSecurity(ev)) {
            //如果事件發生的View在的視窗,沒有被遮擋
            final int action = ev.getAction();
            //重置前面為0 ,只留下後八位,用於判斷相等時候,可以提高效能。
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            //判斷是不是Down事件,如果是的話,就要做初始化操作
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                //如果是down事件,就要清空掉之前的狀態,比如,重置手勢判斷什麼的。
                //比如,之前正在判斷是不是一個單點的滑動,但是第二個down來了,就表示,不可能是單點的滑動,要重新開始判斷觸控的手勢
                //清空掉mFirstTouchTarget
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }


            //檢查是否攔截事件
            final boolean intercepted;
            //如果當前是Down事件,或者已經有處理Touch事件的目標了
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //判斷允不允許這個View攔截
                //使用與運算作為判斷,可以讓我們在flag中,儲存好幾個標誌
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //如果說允許攔截事件
                if (!disallowIntercept) {
                    //確定是不是攔截了
                    intercepted = onInterceptTouchEvent(ev);
                    //重新恢復Action,以免action在上面的步驟被人為地改變了
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                //如果說,事件已經初始化過了,並且沒有子View被分配處理,那麼就說明,這個ViewGroup已經攔截了這個事件
                intercepted = true;
            }

            // Check for cancelation.
            //如果viewFlag被設定了PFLAG_CANCEL_NEXT_UP_EVENT ,那麼就表示,下一步應該是Cancel事件
            //或者如果當前的Action為取消,那麼當前事件應該就是取消了。
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            //如果需要(不是取消,也沒有被攔截)的話,那麼在觸控down事件的時候更新觸控目標列表
            //split代表,當前的ViewGroup是不是支援分割MotionEvent到不同的View當中
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            //新的觸控物件,
            TouchTarget newTouchTarget = null;
            //是否把事件分配給了新的觸控
            boolean alreadyDispatchedToNewTouchTarget = false;
            //事件不是取消事件,也沒有攔截那麼就要判斷
            if (!canceled && !intercepted) {
                //如果是個全新的Down事件
                //或者是有新的觸控點
                //或者是游標來回移動事件(不太明白什麼時候發生)
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    //這個事件的索引,也就是第幾個事件,如果是down事件就是0
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    //獲取分配的ID的bit數量
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    //清理之前觸控這個指標標識,以防他們的目標變得不同步。
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    //如果新的觸控物件為null(這個不是鐵定的嗎)並且當前ViewGroup有子元素
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        //下面所做的工作,就是找到可以接收這個事件的子元素
                        final View[] children = mChildren;
                        //是否使用自定義的順序來新增控制元件
                        final boolean customOrder = isChildrenDrawingOrderEnabled();
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            //如果是用了自定義的順序來新增控制元件,那麼繪製的View的順序和mChildren的順序是不一樣的
                            //所以要根據getChildDrawingOrder取出真正的繪製的View
                            //自定義的繪製,可能第一個會畫到第三個,和第四個,第二個畫到第一個,這樣裡面的內容和Children是不一樣的
                            final int childIndex = customOrder ?
                                    getChildDrawingOrder(childrenCount, i) : i;
                            final View child = children[childIndex];
                            //如果child不可以接收這個觸控的事件,或者觸控事件發生的位置不在這個View的範圍內
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }
                            //獲取新的觸控物件,如果當前的子View在之前的觸控目標的列表當中就返回touchTarget
                            //子View不在之前的觸控目標列表那麼就返回null
                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                //如果新的觸控目標物件不為空,那麼就把這個觸控的ID賦予它,這樣子,
                                //這個觸控的目標物件的id就含有了好幾個pointer的ID了

                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                            //如果子View不在之前的觸控目標列表中,先重置childView的標誌,去除掉CACEL的標誌
                            resetCancelNextUpFlag(child);
                            //呼叫子View的dispatchTouchEvent,並且把pointer的id 賦予進去
                            //如果說,子View接收並且處理了這個事件,那麼就更新上一次觸控事件的資訊,
                            //並且為建立一個新的觸控目標物件,並且繫結這個子View和Pointer的ID
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                mLastTouchDownIndex = childIndex;
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                        }
                    }
                    //如果newTouchTarget為null,就代表,這個事件沒有找到子View去處理它,
                    //那麼,如果之前已經有了觸控物件(比如,我點了一張圖,另一個手指在外面圖的外面點下去)
                    //那麼就把這個之前那個觸控目標定為第一個觸控物件,並且把這個觸控(pointer)分配給最近新增的觸控目標
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            //child view沒有消費觸控事件
            if (mFirstTouchTarget == null) {
                // No tuch targets so treat this as an ordinary view.
                // child view沒有消費事件,把自己當做view來消費
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                //遍歷TouchTargt樹.分發事件,如果我們已經分發給了新的TouchTarget那麼我們就不再分發給newTouchTarget
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        //是否讓child取消處理事件,如果為true,就會分發給child一個ACTION_CANCEL事件
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        //派發事件
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        //cancelChild也就是說,派發給了當前child一個ACTION_CANCEL事件,
                        //那麼就移除這個child
                        if (cancelChild) {
                            //沒有父節點,也就是當前是第一個TouchTarget
                            //那麼就把頭去掉
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                //把下一個賦予父節點的上一個,這樣當前節點就被丟棄了
                                predecessor.next = next;
                            }
                            //回收記憶體
                            target.recycle();
                            //把下一個賦予現在
                            target = next;
                            //下面的兩行不執行了,因為我們已經做了連結串列的操作了。
                            //主要是我們不能執行predecessor=target,因為刪除本節點的話,父節點還是父節點
                            continue;
                        }
                    }
                    //如果沒有刪除本節點,那麼下一輪父節點就是當前節點,下一個節點也是下一輪的當前節點
                    predecessor = target;
                    target = next;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            //遇到了取消事件、或者是單點觸控下情況下手指離開,我們就要更新觸控的狀態
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                //如果是多點觸控下的手指擡起事件,就要根據idBit從TouchTarget中移除掉對應的Pointer(觸控點)
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }

ViewGroup中的dispatchTransformedTouchEvent完整原始碼
dispatchTransformedTouchEvent的主要功能是呼叫childView的dispatchTouchEvent方法完成一輪的事件分發機制,如果dispatchTouchEvent返回true則表示子View消費了事件,false表示子類沒有消費事件,繼續事件分發;

 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
        int oldAction = event.getAction();
        boolean handled;
        if (!cancel && oldAction !=MotionEvent.ACTION_CANCEL) {
            int oldPointerIdBits = event.getPointerIdBits();
            int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
            if (newPointerIdBits == 0) {
                return false;
            } else {
                MotionEvent transformedEvent;
                float offsetX;
                float offsetY;
                if (newPointerIdBits == oldPointerIdBits) {
                    if (child == null || child.hasIdentityMatrix()) {
                    	//child為null時即沒有子View時直接呼叫父類View的dispatchTouchEvent()把自己當做view來消費 ;
                        if (child == null) {
                            handled = super.dispatchTouchEvent(event);
                        } else {
                            offsetX = (float)(this.mScrollX - child.mLeft);
                            offsetY = (float)(this.mScrollY - child.mTop);
                            event.offsetLocation(offsetX, offsetY);
                            //子View不為空繼續呼叫dispatchTouchEvent向下分發;
                            handled = child.dispatchTouchEvent(event);
                            event.offsetLocation(-offsetX, -offsetY);
                        }

                        return handled;
                    }

                    transformedEvent = MotionEvent.obtain(event);
                } else {
                    transformedEvent = event.split(newPointerIdBits);
                }
				// newPointerIdBits != oldPointerIdBits時
                if (child == null) {
                //child為null時即沒有子View時直接呼叫父類View的dispatchTouchEvent()把自己當做view來消費 ;
                    handled = super.dispatchTouchEvent(transformedEvent);
                } else {
                    offsetX = (float)(this.mScrollX - child.mLeft);
                    offsetY = (float)(this.mScrollY - child.mTop);
                    transformedEvent.offsetLocation(offsetX, offsetY);
                    if (!child.hasIdentityMatrix()) {
                        transformedEvent.transform(child.getInverseMatrix());
                    }
					// 子View不為空繼續呼叫dispatchTouchEvent向下分發;
                    handled = child.dispatchTouchEvent(transformedEvent);
                }

                transformedEvent.recycle();
                return handled;
            }
        } else {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
              //child為null時即沒有子View時直接呼叫父類View的dispatchTouchEvent()把自己當做view來消費 ;
                handled = super.dispatchTouchEvent(event);
            } else {
            //子View不為空繼續呼叫dispatchTouchEvent向下分發;
                handled = child.dispatchTouchEvent(event);
            }

            event.setAction(oldAction);
            return handled;
        }
    }

ViewGroup中的addTouchTarget完整原始碼

    private ViewGroup.TouchTarget addTouchTarget(View child, int pointerIdBits) {
        ViewGroup.TouchTarget target = ViewGroup.TouchTarget.obtain(child, pointerIdBits);
        target.next = this.mFirstTouchTarget;
        this.mFirstTouchTarget = target;
        return target;
    }

ViewGroup中的requestDisallowInterceptTouchEvent完整原始碼
用於設定ViewGroup攔截flage

 public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        if (disallowIntercept != ((this.mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            if (disallowIntercept) {
            //重點:就是在這裡給mGroupFlags新增FLAG_DISALLOW_INTERCEPT不允許攔截事件標記位
                this.mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
            } else {
                this.mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
            }

            if (this.mParent != null) {
                this.mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
            }

        }
    }

五、 View中事件分發View中的事件分發機制

public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //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);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }
  public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }