事件分發源碼解析

本文源碼基于6.0

一.Activity中的事件分發。

1.dispatchTouchEvent。

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

第一步:如果是Down事件,執行onUserInteraction()方法,該方法默認是空實現,我們暫且不需要管。
第二步:調用getWindow().superDispatchTouchEvent(ev)。

public Window getWindow() {
        return mWindow;
    }

Window是一個抽象類,在Activity的attach()方法中,我們可以看到這樣一行代碼

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        ......
        mWindow = new PhoneWindow(this);
        ......
    }

PhoneWindow是Window的唯一實現類,所以getWindow().superDispatchTouchEvent(ev)調用的是PhoneWindow的superDispatchTouchEvent(ev)方法。

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

我們可以看到調用了mDecor.superDispatchTouchEvent(event),mDecor是DecorView 的實例,DecorView是PhoneWindow的內部類,繼承FrameLayout,

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

DecorView繼承FrameLayout,因此會調用FrameLayout的dispatchTouchEvent()方法,由于FrameLayout并沒有實現dispatchTouchEvent()方法,因此最終調用的是ViewGroup中的dispatchTouchEvent()方法,這部分后面詳細分析。
第三步:如果有View消費調該事件,即getWindow().superDispatchTouchEvent(ev)返回true,則返回true,如果沒有View消費該事件,即Activity的根視圖以及根視圖的子視圖都沒有攔截該事件的話,即返回false,則調用Activity的onTouchEvent()方法,Activity自己處理。

public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }

Window.java中的shouldCloseOnTouch方法

public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        if (mCloseOnTouchOutside && event.getAction() ==     
                MotionEvent.ACTION_DOWN
                && isOutOfBounds(context, event) && peekDecorView() != null) {
            return true;
        }
        return false;
    }

mCloseOnTouchOutside是一個boolean變量,它是由Window的android:windowCloseOnTouchOutside屬性值決定。
isOutOfBounds(context, event)是判斷該event的坐標是否在當前的Activity之外。是的話,返回true;否則,返回false。
peekDecorView()返回當前的DecorView。
也就是說:如果設置了android:windowCloseOnTouchOutside屬性為true,并且當前事件是ACTION_DOWN,而且點擊發生在Activity之外,同時Activity還包含視圖的話,則返回true;表示該點擊事件會導致Activity的結束。

二.ViewGroup的事件分發

在上面的分析中我們說道getWindow().superDispatchTouchEvent(ev)最終會調到ViewGroup中的dispatchTouchEvent(event)方法中,省略部分代碼。

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        ......
        boolean handled = false;
        // 第一步
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            //第二步
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            //第三步
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                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;
            }

            if (intercepted || mFirstTouchTarget != null) {
                ev.setTargetAccessibilityFocus(false);
            }

            // Check for cancelation.
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

            // Update list of touch targets for pointer down, if needed.
            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    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;
                    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 ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(childIndex);

                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            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.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    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.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary 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;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            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) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

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

我們一步一步來分析:
第一步:首先調用onFilterTouchEventForSecurity(ev)來判斷是否要分發該事件,該方法的實現在View中。

public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //noinspection RedundantIfStatement
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }

如果該View不是位于頂部,并且有設置屬性使該View不在頂部時不響應觸摸事件,則不分發該觸摸事件,即返回false。 否則,則對觸摸事件進行分發,即返回true。
第二步:如果是down事件,清空之前的狀態。
這里有必要說一下mFirstTouchTarget,它是接受觸摸事件的View所組成的單鏈表。

private static final class TouchTarget {
        private static final int MAX_RECYCLED = 32;
        private static final Object sRecycleLock = new Object[0];
        private static TouchTarget sRecycleBin;
        private static int sRecycledCount;

        public static final int ALL_POINTER_IDS = -1; // all ones

        // 被觸摸的view
        public View child;

        // pointerIdBits是記錄觸摸事件的id信息(對于多指觸摸而言)
        public int pointerIdBits;

        // The next target in the target list.
        public TouchTarget next;

        private TouchTarget() {
        }

        public static TouchTarget obtain(View child, int pointerIdBits) {
            final TouchTarget target;
            synchronized (sRecycleLock) {
                if (sRecycleBin == null) {
                    target = new TouchTarget();
                } else {
                    target = sRecycleBin;
                    sRecycleBin = target.next;
                     sRecycledCount--;
                    target.next = null;
                }
            }
            target.child = child;
            target.pointerIdBits = pointerIdBits;
            return target;
        }

        public void recycle() {
            synchronized (sRecycleLock) {
                if (sRecycledCount < MAX_RECYCLED) {
                    next = sRecycleBin;
                    sRecycleBin = this;
                    sRecycledCount += 1;
                } else {
                    next = null;
                }
                child = null;
            }
        }
    }
private void cancelAndClearTouchTargets(MotionEvent event) {
        if (mFirstTouchTarget != null) {
            ......
            for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
                resetCancelNextUpFlag(target.child);
                dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
            }
            clearTouchTargets();

            if (syntheticEvent) {
                event.recycle();
            }
        }
    }
private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        final int oldAction = event.getAction();
        //如果cancel為true或者action為ACTION_CANCEL,設置事件為cancel,并將      
        //事件分發出去
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            //如果child為null,則調用super.dispatchTouchEvent(event),即View中的 
            //dispatchTouchEvent(event),如果不為null,則將該事件分發給子孩子。
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
        if (newPointerIdBits == 0) {
            return false;
        }
        final MotionEvent transformedEvent;
        // 如果計算得到的前后觸摸事件id信息相同,則執行不需要重新計算 
        //MotionEvent,直接執行if語句塊進行消費分發;
        // 否則,就重新計算MotionEvent之后,再進行消息分發。
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);
                    handled = child.dispatchTouchEvent(event);
                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }
        if (child == null) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }
        transformedEvent.recycle();
        return handled;
    }

清空mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVEN標記

private static boolean resetCancelNextUpFlag(View view) {
        if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
            view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
            return true;
        }
        return false;
    }

清空mFirstTouchTarget鏈表,并設置mFirstTouchTarget為null

private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

總結:cancelAndClearTouchTargets作用就是遍歷mFirstTouchTarget鏈表,清空鏈表中的每一個view的PFLAG_CANCEL_NEXT_UP_EVENT標記。
第三步:是否需要攔截事件
如果是down事件或者mFirstTouchTarget不為null,執行if中的代碼,首先判斷是否禁止ViewGroup進行事件攔截,檢查FLAG_DISALLOW_INTERCEPT標記,如果調用了requestDisallowInterceptTouchEvent()標記的話,則FLAG_DISALLOW_INTERCEPT會為true。
如果disallowIntercept為true的話,則intercepted = fasle,反之調用onInterceptTouchEvent(ev)方法

   public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

第四步:檢查當前事件是否取消

final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

對于ACTION_DOWN來說,mPrivateFlags的PFLAG_CANCEL_NEXT_UP_EVENT位肯定是0;因此,canceled=false。
當前的View或ViewGroup要被從父View中detach時,PFLAG_CANCEL_NEXT_UP_EVENT就會被設為true;此時,它就不再接受觸摸事件。
第五步:將事件分發給子view。

       TouchTarget newTouchTarget = null;
       boolean alreadyDispatchedToNewTouchTarget = false;
       //如果事件沒有被取消并且事件沒有被攔截,則將事件分發給子view
       if (!canceled && !intercepted) {
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                //事件為ACTION_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
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //從后向前遍歷
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(childIndex);

                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
                            //如果該child可以接受事件(該child是VISIBLE的或者該child不是    
                           //VISIBLE的,但是位于動畫狀態)并且觸摸左邊落在child的可視范 
                           //圍內,則繼續執行,否則continue。
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //查找child是否存在于mFirstTouchTarget鏈表中
                            newTouchTarget = getTouchTarget(child);
                            //如果存在跳出for循環
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                           // 重置child的mPrivateFlags變量中的 
                   PFLAG_CANCEL_NEXT_UP_EVENT位。
                            resetCancelNextUpFlag(child);
                           //將事件分發給子view
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //如果該child可以接受該事件,即該child攔截了該事件或者消費 
                                //了該事件并返回true,將該view添加到mFirstTouchTarget鏈表表頭
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                               //將alreadyDispatchedToNewTouchTarget置為true。
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }
                    //如果newTouchTarget == null并且mFirstTouchTarget != null,將mFirstTouchTarget中第一個不為null的節點設置給newTouchTarget
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

第六步:進一步進行事件分發

            if (mFirstTouchTarget == null) {
                //如果mFirstTouchTarget == null,意味著沒有子孩子可以接受該事件,調用dispatchTransformedTouchEvent方法,注意傳的參 
                //數,第三個參數為null,則會調用super.dispatchTouchEvent(event),也就是調用View.dispatchTouchEvent(event),由于ViewGroup沒有覆蓋onTouchEvent(event),最終會調用View.onTouchEvent(event)。
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                //如果如果mFirstTouchTarget != null,說明又可以接受事件的子孩子。
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        //如果alreadyDispatchedToNewTouchTarget為true(說明已經分發過),并且target等于newTouchTarget,則直接返回true
                        handled = true;
                    } else {
                        //否則分發事件給子view,主要針對MOVE和UP事件
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

總結一下:
down事件時,相當于一次事件的開始,會清空之前的標記和mFirstTouchTarget鏈表。
ViewGroup默認不攔截事件。
子元素可以通過調用ViewGroup的requestDisallowInterceptTouchEvent方法修改FLAG_DISALLOW_INTERCEPT標志位的值請求不要攔截該事件,但ACTION_DOWN事件除外,因為在ACTION_DOWN事件FLAG_DISALLOW_INTERCEPT標志位會被重置。
第五步中遍歷子孩子并將事件分發給可以接受事件的子孩子,注意只有down事件時才會執行第五步。此時會把可以接受事件的子孩子保存到
mFirstTouchTarget鏈表中。
如果某個子孩子沒有接受down事件,即該子孩子沒有保存到
mFirstTouchTarget鏈表中,那么該子孩子也不會接受到move和up事件。

三.View的事件分發。

由上面的邏輯可知,最終會調用到View的dispatchTouchEvent()方法。

public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        boolean result = false;
         //如果該View不是位于頂部,并且有設置屬性使該View不在頂部時不響應觸摸事件,則不分發該觸摸事件,即返回false。 否則,則對觸摸事件進行分發,即返回true。
        if (onFilterTouchEventForSecurity(event)) {
            ListenerInfo li = mListenerInfo;
            //如果li不為null、并且設置了OnTouchListener、并且View是可點擊的、并且OnTouchListener.onTouch()方法返回true,則返回true。
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //調用onTouchEvent(event),如果onTouchEvent(event)返回true,則返回true,否則返回false。
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        ......
        return result;
    }

下面是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();
        //當view的狀態為DISABLED(被禁用,調用setEnabled(false)時,View就被禁用了)時,返回它是否時可點擊的(仍然會消費掉事件,只是沒有效果而已)
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        //當我們設置了TouchDelegate監聽代理時,會調用mTouchDelegate.onTouchEvent(event),類似于擴大點擊范圍時,mTouchDelegate默認情況下為null。
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //如果不可點擊(既不能單擊,也不能長按)則直接返回false
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                // 下面事件分開講解
            }

            return true;
        }

        return false;
    }

case MotionEvent.ACTION_DOWN:

case MotionEvent.ACTION_DOWN:
        //處理長按事件標識
        mHasPerformedLongPress = false;

        if (performButtonActionOnTouchDown(event)) {
               break;
        }
        //判斷是否正在滾動的容器中,不能把滑動當前點擊.所以先判斷是不是在一個可滑動的容器中
        boolean isInScrollingContainer = isInScrollingContainer();
        if (isInScrollingContainer) {
           //如果是在一個可滾動的容器中,先設置用戶準備點擊這么一個標志位:PFLAG_PREPRESSED,然后則發送一個延遲消息來確定用戶到底是要滾動還是點擊
            mPrivateFlags |= PFLAG_PREPRESSED;
            if (mPendingCheckForTap == null) {
                  mPendingCheckForTap = new CheckForTap();
            }
            mPendingCheckForTap.x = event.getX();
            mPendingCheckForTap.y = event.getY();
            //在給定的tapTimeout時間之內,用戶的觸摸沒有移動,就當作用戶是想點擊,而不是滑動
            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
         } else {
             //如果不是在一個可滾動的容器中,調用setPressed(true) 設置按下狀態.,setPressed 主要是設置PFLAG_PRESSED標志位,檢查長按。
             setPressed(true, x, y);
             checkForLongClick(0);
         }
         break;

檢查點擊還是滑動的具體做法:將 CheckForTap的實例mPendingCheckForTap添加時消息隊例中,延遲執行.
如果在這tagTimeout之間用戶觸摸移動了,則刪除此消息.否則:執行按下狀態.然后檢查長按,檢查長按事件的思路也類似。
CheckForTap消息方法如下:

private final class CheckForTap implements Runnable {
        public float x;
        public float y;

        @Override
        public void run() {
            mPrivateFlags &= ~PFLAG_PREPRESSED;
            setPressed(true, x, y);
            checkForLongClick(ViewConfiguration.getTapTimeout());
        }
    }

case MotionEvent.ACTION_MOVE:

case MotionEvent.ACTION_MOVE:
        drawableHotspotChanged(x, y);
        //判斷觸摸點是否在此view中,先將上下左右增大mTouchSlop個像素,再判斷。
        if (!pointInView(x, y, mTouchSlop)) {
              //如果超出view之外,將處理點擊消息移除
             removeTapCallback();
             if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                  //如果是已經準備長按了,則將長按的消息移除.并將View的按下狀態設置為false。
                 removeLongPressCallback();
                 setPressed(false);
             }
         }
        break;

case MotionEvent.ACTION_UP:

case MotionEvent.ACTION_UP:
       boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
       //首先是檢查 PFLAG_PREPRESSED 和PFLAG_PRESSED 這兩個標志.如果其中一個為真則處理。
       //這兩個標志位首先是在開始觸控時(即手指按下ACTION_DOWN)時設置,PFLAG_PREPRESSED 表示在一個可滾動的容器中,要稍后才能確定是按下還是滾動,PFLAG_PRESSED 表示不是在一個可滾動的容器中,已經可以確定按下這一操作。
       if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
           boolean focusTaken = false;
           if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
              focusTaken = requestFocus();
           }

           if (prepressed) {
              setPressed(true, x, y);
           }
            //判斷是否進行了長按,如果沒有,則移除長按
           if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
               removeLongPressCallback();
               //判斷有沒有重新請求獲得焦點,如果還沒有新獲得焦點,說明之前已經是按下的狀態了。
               if (!focusTaken) {
                  if (mPerformClick == null) {
                     mPerformClick = new PerformClick();
                  }
                  //派發執行點擊操作的消息
                  if (!post(mPerformClick)) {
                     performClick();
                  }
                }
            }

            if (mUnsetPressedState == null) {
                mUnsetPressedState = new UnsetPressedState();
            }
            //派發消息來取消點擊狀態
            if (prepressed) {
                 postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
             } else if (!post(mUnsetPressedState)) {
                  mUnsetPressedState.run();
             }

             removeTapCallback();
        }
        mIgnoreNextUpEvent = false;
        break;
private final class PerformClick implements Runnable {
        @Override
        public void run() {
            performClick();
        }
    }
public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        //如果li不為null并且設置了OnClickListener,則執行onClick()方法。
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

case MotionEvent.ACTION_CANCEL:

 //重置按鈕狀態及變量的值,移除點擊和長按的檢查
  case MotionEvent.ACTION_CANCEL:
       setPressed(false);
       removeTapCallback();
       removeLongPressCallback();
       mInContextButtonPress = false;
       mHasPerformedLongPress = false;
       mIgnoreNextUpEvent = false;
       break;

好了,事件分發的源碼分析就到此結束了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容