事件分發(fā)源碼解析

本文源碼基于6.0

一.Activity中的事件分發(fā)。

1.dispatchTouchEvent。

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

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

public Window getWindow() {
        return mWindow;
    }

Window是一個(gè)抽象類,在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的唯一實(shí)現(xiàn)類,所以getWindow().superDispatchTouchEvent(ev)調(diào)用的是PhoneWindow的superDispatchTouchEvent(ev)方法。

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

我們可以看到調(diào)用了mDecor.superDispatchTouchEvent(event),mDecor是DecorView 的實(shí)例,DecorView是PhoneWindow的內(nèi)部類,繼承FrameLayout,

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

DecorView繼承FrameLayout,因此會(huì)調(diào)用FrameLayout的dispatchTouchEvent()方法,由于FrameLayout并沒有實(shí)現(xiàn)dispatchTouchEvent()方法,因此最終調(diào)用的是ViewGroup中的dispatchTouchEvent()方法,這部分后面詳細(xì)分析。
第三步:如果有View消費(fèi)調(diào)該事件,即getWindow().superDispatchTouchEvent(ev)返回true,則返回true,如果沒有View消費(fèi)該事件,即Activity的根視圖以及根視圖的子視圖都沒有攔截該事件的話,即返回false,則調(diào)用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是一個(gè)boolean變量,它是由Window的android:windowCloseOnTouchOutside屬性值決定。
isOutOfBounds(context, event)是判斷該event的坐標(biāo)是否在當(dāng)前的Activity之外。是的話,返回true;否則,返回false。
peekDecorView()返回當(dāng)前的DecorView。
也就是說:如果設(shè)置了android:windowCloseOnTouchOutside屬性為true,并且當(dāng)前事件是ACTION_DOWN,而且點(diǎn)擊發(fā)生在Activity之外,同時(shí)Activity還包含視圖的話,則返回true;表示該點(diǎn)擊事件會(huì)導(dǎo)致Activity的結(jié)束。

二.ViewGroup的事件分發(fā)

在上面的分析中我們說道getWindow().superDispatchTouchEvent(ev)最終會(huì)調(diào)到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;
    }

我們一步一步來分析:
第一步:首先調(diào)用onFilterTouchEventForSecurity(ev)來判斷是否要分發(fā)該事件,該方法的實(shí)現(xiàn)在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不是位于頂部,并且有設(shè)置屬性使該View不在頂部時(shí)不響應(yīng)觸摸事件,則不分發(fā)該觸摸事件,即返回false。 否則,則對觸摸事件進(jìn)行分發(fā),即返回true。
第二步:如果是down事件,清空之前的狀態(tài)。
這里有必要說一下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,設(shè)置事件為cancel,并將      
        //事件分發(fā)出去
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            //如果child為null,則調(diào)用super.dispatchTouchEvent(event),即View中的 
            //dispatchTouchEvent(event),如果不為null,則將該事件分發(fā)給子孩子。
            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;
        // 如果計(jì)算得到的前后觸摸事件id信息相同,則執(zhí)行不需要重新計(jì)算 
        //MotionEvent,直接執(zhí)行if語句塊進(jìn)行消費(fèi)分發(fā);
        // 否則,就重新計(jì)算MotionEvent之后,再進(jìn)行消息分發(fā)。
        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標(biāo)記

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鏈表,并設(shè)置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;
        }
    }

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

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

第四步:檢查當(dāng)前事件是否取消

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

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

       TouchTarget newTouchTarget = null;
       boolean alreadyDispatchedToNewTouchTarget = false;
       //如果事件沒有被取消并且事件沒有被攔截,則將事件分發(fā)給子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的,但是位于動(dòng)畫狀態(tài))并且觸摸左邊落在child的可視范 
                           //圍內(nèi),則繼續(xù)執(zhí)行,否則continue。
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
                            //查找child是否存在于mFirstTouchTarget鏈表中
                            newTouchTarget = getTouchTarget(child);
                            //如果存在跳出for循環(huán)
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
                           // 重置child的mPrivateFlags變量中的 
                   PFLAG_CANCEL_NEXT_UP_EVENT位。
                            resetCancelNextUpFlag(child);
                           //將事件分發(fā)給子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攔截了該事件或者消費(fèi) 
                                //了該事件并返回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中第一個(gè)不為null的節(jié)點(diǎn)設(shè)置給newTouchTarget
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

第六步:進(jìn)一步進(jìn)行事件分發(fā)

            if (mFirstTouchTarget == null) {
                //如果mFirstTouchTarget == null,意味著沒有子孩子可以接受該事件,調(diào)用dispatchTransformedTouchEvent方法,注意傳的參 
                //數(shù),第三個(gè)參數(shù)為null,則會(huì)調(diào)用super.dispatchTouchEvent(event),也就是調(diào)用View.dispatchTouchEvent(event),由于ViewGroup沒有覆蓋onTouchEvent(event),最終會(huì)調(diào)用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(說明已經(jīng)分發(fā)過),并且target等于newTouchTarget,則直接返回true
                        handled = true;
                    } else {
                        //否則分發(fā)事件給子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;
                }
            }

總結(jié)一下:
down事件時(shí),相當(dāng)于一次事件的開始,會(huì)清空之前的標(biāo)記和mFirstTouchTarget鏈表。
ViewGroup默認(rèn)不攔截事件。
子元素可以通過調(diào)用ViewGroup的requestDisallowInterceptTouchEvent方法修改FLAG_DISALLOW_INTERCEPT標(biāo)志位的值請求不要攔截該事件,但ACTION_DOWN事件除外,因?yàn)樵贏CTION_DOWN事件FLAG_DISALLOW_INTERCEPT標(biāo)志位會(huì)被重置。
第五步中遍歷子孩子并將事件分發(fā)給可以接受事件的子孩子,注意只有down事件時(shí)才會(huì)執(zhí)行第五步。此時(shí)會(huì)把可以接受事件的子孩子保存到
mFirstTouchTarget鏈表中。
如果某個(gè)子孩子沒有接受down事件,即該子孩子沒有保存到
mFirstTouchTarget鏈表中,那么該子孩子也不會(huì)接受到move和up事件。

三.View的事件分發(fā)。

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

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

下面是onTouchEvent()的實(shí)現(xiàn),該方法會(huì)把點(diǎn)擊和滑動(dòng)區(qū)分出來,會(huì)把點(diǎn)擊和長按事件區(qū)分出來。

public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        //當(dāng)view的狀態(tài)為DISABLED(被禁用,調(diào)用setEnabled(false)時(shí),View就被禁用了)時(shí),返回它是否時(shí)可點(diǎn)擊的(仍然會(huì)消費(fèi)掉事件,只是沒有效果而已)
        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);
        }
        //當(dāng)我們設(shè)置了TouchDelegate監(jiān)聽代理時(shí),會(huì)調(diào)用mTouchDelegate.onTouchEvent(event),類似于擴(kuò)大點(diǎn)擊范圍時(shí),mTouchDelegate默認(rèn)情況下為null。
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //如果不可點(diǎn)擊(既不能單擊,也不能長按)則直接返回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:
        //處理長按事件標(biāo)識
        mHasPerformedLongPress = false;

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

檢查點(diǎn)擊還是滑動(dòng)的具體做法:將 CheckForTap的實(shí)例mPendingCheckForTap添加時(shí)消息隊(duì)例中,延遲執(zhí)行.
如果在這tagTimeout之間用戶觸摸移動(dòng)了,則刪除此消息.否則:執(zhí)行按下狀態(tài).然后檢查長按,檢查長按事件的思路也類似。
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);
        //判斷觸摸點(diǎn)是否在此view中,先將上下左右增大mTouchSlop個(gè)像素,再判斷。
        if (!pointInView(x, y, mTouchSlop)) {
              //如果超出view之外,將處理點(diǎn)擊消息移除
             removeTapCallback();
             if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                  //如果是已經(jīng)準(zhǔn)備長按了,則將長按的消息移除.并將View的按下狀態(tài)設(shè)置為false。
                 removeLongPressCallback();
                 setPressed(false);
             }
         }
        break;

case MotionEvent.ACTION_UP:

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

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

            if (mUnsetPressedState == null) {
                mUnsetPressedState = new UnsetPressedState();
            }
            //派發(fā)消息來取消點(diǎn)擊狀態(tài)
            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并且設(shè)置了OnClickListener,則執(zhí)行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:

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

好了,事件分發(fā)的源碼分析就到此結(jié)束了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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