View 的事件分發(fā) 原理和源碼分析

很多人都是學(xué)了忘交掏,忘了學(xué),感覺(jué)永遠(yuǎn)也記不住刃鳄。View 事件分發(fā)到底應(yīng)該怎么學(xué)盅弛?

其實(shí)很簡(jiǎn)單:

1、敲代碼
2叔锐、學(xué)習(xí)原理
3挪鹏、畫(huà)流程圖

View 事件分發(fā) 其實(shí)就4?部分組成

1、 dispatchTouchEvent(MotionEvent ev)

2愉烙、 onInterceptTouchEvent(MotionEvent ev)

3叠纹、 onTouchEvent(MotionEvent ev)

4搭伤、 MotionEvent

那么我們就詳細(xì)的了解一下這四個(gè)的具體含義。


dispatchTouchEvent(MotionEvent ev)

從名字就可以看出這個(gè)方法主要用來(lái)事件的分發(fā)鳞芙,每個(gè) View 都有這個(gè)方法姚建,如果消息能夠傳遞到這個(gè) View,那么這個(gè)方法一定能執(zhí)行。返回 true 代表消耗這個(gè)事件,false 表示不消耗振乏。這個(gè)結(jié)果受兩個(gè)因素的影響。

1秉扑、當(dāng)前 View 的 onTouchEvent() 的返回值慧邮。
2、下級(jí) View dispatchTouchEvent() 方法的返回值

onInterceptTouchEvent(MotionEvent ev)

這個(gè)方法是在dispatchTouchEvent() 中調(diào)用舟陆,误澳,事件攔截,這個(gè)只有在 ViewGroup 才有方法秦躯,View 是沒(méi)有的忆谓。如果當(dāng)前 View 攔截了這個(gè)事件,那么在一個(gè)事件序列中這個(gè)方法不會(huì)被再次調(diào)用了踱承。直到這個(gè)事件序列結(jié)束倡缠。返回的結(jié)果表示是否消耗了這個(gè)事件。

onTouchEvent(MotionEvent ev)

這個(gè)方法是在dispatchTouchEvent() 中調(diào)用勾扭,用來(lái)處理點(diǎn)擊事件毡琉,返回結(jié)果表示是否消耗了這個(gè)事件铁瞒。如果不消耗妙色,返回 false 那么在同一系列事件中,將不會(huì)再給這個(gè)事件派發(fā)慧耍,也就是再次收到事件身辨。

在藝術(shù)探索一書(shū)中有這么一段偽代碼

public boolean dispatchTouchEvent(MotionEvent ev){
      boolean consume = false;
      if(onInterceptTouchEvent(ev)){
        consume = onTouchEvent(ev);
      }else{
       consume = child.dispatchTouchEvent(ev);
      }
      return consume;    
}

我們可以通過(guò)記憶這一段簡(jiǎn)單的偽代碼,理解整個(gè) View 的事件分發(fā)芍碧。


1煌珊、一個(gè)事件序列
事件序列
2、事件分發(fā)順序
事件分發(fā)順序
事件分發(fā)流程

通過(guò)偽代碼和圖示我們大概了解了 View 的事件分發(fā)泌豆,但是更具體的還需要源碼去分析定庵。

我們便從 Activity 到 View 一起來(lái)走一遍,看看具體代碼是怎么實(shí)現(xiàn)的踪危,里面有什么細(xì)節(jié)給我們解惑的蔬浙。

1、Activity

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

我們結(jié)合上圖和源碼贞远,可以看到很簡(jiǎn)單的幾段代碼畴博,事件分發(fā)調(diào)用的是 Windowd 的superDispatchTouchEvent 分發(fā)。接著往下看蓝仲。
我們知道 Window 是個(gè)抽象類(lèi)俱病,所以用實(shí)現(xiàn)類(lèi) PhoneWindow官疲。

  mWindow = new PhoneWindow(this, window, activityConfigCallback);

PhoneWindow 又托管給 DecorView ,就是我們說(shuō)的 RootView 或者 RootViewGroup亮隙。

    mDecor = (DecorView) preservedWindow.getDecorView();
          @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

其實(shí)就是 ViewGroup 的dispatchTouchEvent 方法途凫。

   public boolean dispatchTouchEvent(MotionEvent ev) {
      ······
//從這里開(kāi)始 默認(rèn) handled 為 false  表示不消耗
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
              //當(dāng)事件 是 按下的時(shí)候 也是這一事件系列的開(kāi)始
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // 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.
                //  mFirstTouchTarget = null;重置mFirstTouchTarget 和 FLAG_DISALLOW_INTERCEPT
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // 表示是否攔截
            final boolean intercepted;
        //當(dāng)事件類(lèi)型為 ACTION_DOWN 或者 mFirstTouchTarget 不為空的時(shí)候
//什么是 mFirstTouchTarget ?整體分析可知 當(dāng)這個(gè)事件被子 view 消耗的時(shí)候咱揍,
//mFirstTouchTarget 會(huì)指向這個(gè) view颖榜,也就是第一個(gè)消耗這個(gè)事件的 view。
//當(dāng) ViewGroup 不攔截的時(shí)候且子 view消耗了這個(gè)事件 那么mFirstTouchTarget 煤裙!=null掩完。
//那么 當(dāng)事件類(lèi)型為 MOVE 或者 UP 的時(shí)候,如果 mFirstTouchTarget 硼砰!=null 那就說(shuō)明
//有 view 消耗這個(gè)事件了且蓬,該系列的事件都默認(rèn)交給它處理
//也就是事件的開(kāi)始 和 事件有消耗后 都會(huì)走這個(gè)方法。當(dāng)然也不是絕對(duì)的
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
//FLAG_DISALLOW_INTERCEPT這個(gè)參數(shù)是子 view 通過(guò)requestDisallowInterceptTouchEvent 方法题翰,改變這個(gè)值 恶阴,來(lái)控制 父 view 是否攔截
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//當(dāng)需要攔截的時(shí)候 那就走自己的onInterceptTouchEvent 方法具體自己實(shí)現(xiàn) ,不需要那就intercepted 為 false
//需要注意的是  對(duì)于 DOWN 子 view 通過(guò) requestDisallowInterceptTouchEvent 方法是控制不了父 view 的攔截方法的豹障,因?yàn)楫?dāng)有 DOWN 事件的時(shí)候 就會(huì)觸發(fā) 之前的重置方法冯事。
//所以只能針對(duì) MOVE UP 等,可以讓 父 view 不在攔截事件血公,可以使父 view 開(kāi)始攔截事件
//正是因?yàn)?FLAG_DISALLOW_INTERCEPT 昵仅,如果我們父 view 開(kāi)始攔截,并消耗了這個(gè)事件累魔,那么 mFirstTouchTarget摔笤!=null 就不會(huì)成立,因?yàn)閂iewGroup 的子 view 成功消耗這個(gè)事件后 才會(huì)mFirstTouchTarget垦写!=null吕世。所以以后的事件也就不會(huì)再走 onInterceptTouchEvent,直接返回  intercepted = true 梯投。
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    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.
                intercepted = true;
            }

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            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;
//當(dāng)ViewGroup不攔截時(shí)命辖,這里對(duì) ViewGroup 的子 view 進(jìn)行處理 
            if (!canceled && !intercepted) {

                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                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 = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //遍歷子 view
                        for (int i = childrenCount - 1; i >= 0; i--) {
//這一堆的代碼有點(diǎn)復(fù)雜 不過(guò)就一個(gè)目的 當(dāng)前子 view 是都能接受點(diǎn)擊事件
//如何判斷呢? 1分蓖、是否在播放動(dòng)畫(huà) 2尔艇、點(diǎn)擊的坐標(biāo)是否在該子 view 的區(qū)域
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
//這就是判斷是否可以點(diǎn)擊的方法之一    view 要顯示,是否在播放動(dòng)畫(huà)
  /**
     * Returns true if a child view can receive pointer events.
     * @hide 
    * private static boolean canViewReceivePointerEvents(@NonNull View child) {
    *     return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
     *            || child.getAnimation() != null;
    * }
 */
//判斷二  是否在子 view 的范圍
/**
* protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }
*
*/
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
//mFirstTouchTarget 是一個(gè)鏈表形式 遍歷這個(gè)鏈表 如果有這個(gè) view 那么就直接賦值 newTouchTarget   
//注意 mFirstTouchTarget 是在View 的子 view中賦值的
// 說(shuō)明點(diǎn)擊確實(shí)在子 view 的區(qū)域 直接返回不用再繼續(xù)遍歷了
                            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);
//實(shí)際是 調(diào)用 子 view  的 dispatchTouchEvent 方法咆疗。依次遞歸下去
//如果 子 view dispatchTouchEvent 返回true 那么  newTouchTarget = addTouchTarget(child, idBitsToAssign);
// newTouchTarget 被賦值并跳出循環(huán)  其實(shí)是 mFirstTouchTarget鏈表添加數(shù)據(jù)
                                alreadyDispatchedToNewTouchTarget = true;  
                            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();
                    }
//當(dāng) newTouchTarget 還是null 那就 鏈表 最后一個(gè) 賦值給newTouchTarget
                    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.
//當(dāng) mFirstTouchTarget 為空 其實(shí)也就是子 view 為空漓帚,調(diào)用 view 的 dispatchTouchEvent 方法 
//就是 viewGroup 如果需要消耗事件 其實(shí)調(diào)用的是 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.
//更新重置 狀態(tài) 
            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;
    }

我們來(lái)看下 ViewGroup 的分發(fā)。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
//當(dāng)沒(méi)有子 view 那么它就相當(dāng)于 view 調(diào)用 view 的方法午磁。
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
//如果有 那么就 遞歸這個(gè)方法了
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
//當(dāng)沒(méi)有子 view 那么它就相當(dāng)于 view 調(diào)用 view 的方法尝抖。
                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);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {
//當(dāng)沒(méi)有子 view 那么它就相當(dāng)于 view 調(diào)用 view 的方法毡们。
            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);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

其實(shí)我們發(fā)現(xiàn)這一切都和 mFirstTouchTarget 有關(guān)系,當(dāng) viewGroup 需要自己消耗事件的時(shí)候昧辽,那么 mFirstTouchTarget 肯定是 null衙熔,也就是子 view 不消耗,或者自己攔截搅荞,都會(huì)造成 mFirstTouchTarget ==null红氯,那么當(dāng)調(diào)用 dispatchTransformedTouchEvent 方法是,就會(huì)觸發(fā) view 的 onTouchEvent 方法咕痛,viewGroup 自己去處理事件痢甘。

首先我們看看 View 的 dispatchTouchEvent() 方法(只保留了我們需要的源碼)。

 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);
        }
//這里是開(kāi)始 
        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;
  //判斷是否有監(jiān)聽(tīng)事件  并且 mOnTouchListener不為空
            //view 的  enabled 為true 并且onTouch方法也返回true
          //那么直接返回 true
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
//當(dāng) onTouch 被調(diào)動(dòng) 且返回true 那么onTouchEvent不會(huì)調(diào)用了
            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;
    }

我們會(huì)發(fā)現(xiàn) View 可以調(diào)用 onTouch() 且返回 true, onTouchEvent就不會(huì)再調(diào)用了茉贡,這就是一個(gè)優(yōu)先級(jí)的問(wèn)題了塞栅。

最后是 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();
//是否可點(diǎn)擊   
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//就算   不可用的情況下 也可能會(huì)消耗點(diǎn)擊事件   只要 clickable 為true
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }
//如果 view 有代理會(huì)做這個(gè)方法 具體不說(shuō)了 
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
 //只要控件的clickable 或者 long_clickable 或者 CONTEXT_CLICKABLE 有一個(gè)為 true 
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
                        handleTooltipUp();
                    }
                    if (!clickable) {
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        break;
                    }
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
//這是 up  所以 長(zhǎng)按事件回調(diào)移除
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
// 我們?cè)O(shè)置點(diǎn)擊事件 是在這兒 開(kāi)始的~~看好了  我們發(fā)現(xiàn)是通過(guò) post  把 run 里的調(diào)用 performClick() 方法。用 Runnable 腔丧,通過(guò) post 形式執(zhí)行 performClick放椰,而不是直接調(diào)用 performClick , 因?yàn)檫@可以讓這個(gè) view 更新的視覺(jué)狀態(tài)在這個(gè)方法執(zhí)行之前愉粤。
//這個(gè)比如當(dāng)這個(gè) view 繪制 顯示 完成之后 才可以 點(diǎn)擊 之類(lèi)的 等等 
                                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)) {
                            // If the post failed, unpress right now
                            mUnsetPressedState.run();
                        }

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }

                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();

                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0, x, y);
                    }
                    break;

                case MotionEvent.ACTION_CANCEL:
                    if (clickable) {
                        setPressed(false);
                    }
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    break;

                case MotionEvent.ACTION_MOVE:
                    if (clickable) {
                        drawableHotspotChanged(x, y);
                    }

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        // Remove any future long press/tap checks
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }
//當(dāng) clickable 為 true 那么一定返回 true 不管執(zhí)行了 點(diǎn)擊事件啥 或者 其他 這個(gè)事件在這兒一定會(huì)消耗  
//就是返回 true 所以當(dāng)設(shè)置 不可點(diǎn)擊的時(shí)候且clickable 為false 才會(huì)不消耗這個(gè)事件
            return true;
        }

        return false;
    }

超級(jí)會(huì)員:mOnTouchListener
會(huì)員:onTouchEvent
普通用戶(hù):setOnClickListener

最后根據(jù)上面的源碼分析后砾医,我們整理一些結(jié)論,結(jié)合結(jié)論和源碼分析衣厘,我們更好的理解 View的事件分發(fā)如蚜。(copy from 《藝術(shù)探索》 + 自己的理解)

1、同一個(gè)事件序列是從手指觸摸屏幕的那一刻開(kāi)始头滔,直到手指離開(kāi)屏幕的那一刻結(jié)束怖亭,在這個(gè)過(guò)程產(chǎn)生的一系列事件涎显,是從 down 開(kāi)始 經(jīng)過(guò) n 個(gè) move 最后 up 結(jié)束坤检。

2、正常情況下期吓,一個(gè)事件只能被一個(gè) View 攔截且消耗早歇,原因參考 3。一旦一個(gè) View 攔截了某事件讨勤,那么這個(gè)事件序列內(nèi)的所有事物都會(huì)直接交給它處理箭跳,因此同一個(gè)事件序列中的事件不能由兩個(gè) view 同時(shí)處理,注意是同時(shí)潭千,但是通過(guò)特殊手段可是谱姓,比如將一個(gè)View 的事件強(qiáng)行傳遞給其他view,這是無(wú)賴(lài)操作刨晴。

3屉来、某個(gè) View 一旦決定攔截路翻,那么這個(gè)事件序列只能由他來(lái)出來(lái),如果事件能傳遞給他茄靠,而且他的 onInterceptTouchEvent 不會(huì)再繼續(xù)調(diào)用茂契,因?yàn)?View 攔截那么mFirstTouchTarget 就不會(huì)在 子 View 中賦值,mFirstTouchTarget ==null 直接返回 true 慨绳,最后走 View 的 ouTouchEvent 方法掉冶。

4、某個(gè) View 一旦開(kāi)始處理事件脐雪,如果它不消耗 ACTION_DOWN 事件( onTouchEvent 返回 false)厌小,那么同一序列的而其他事件都不會(huì)交給他處理,而是將事件重新交給他的父元素處理战秋,即 父 View 的 onTouchEvent 會(huì)調(diào)用 召锈。源碼上其實(shí)就是 比如子 View onTouchEvent 返回 false,因?yàn)?dispatchTouchEvent 是 遞歸的获询,所以返回 子 view 的dispatchTouchEvent 返回 false涨岁,最后 mFirstTouchTarget 還是null ,所以 父 view(ViewGroup) 調(diào)用 View 的 dispatchTouchEvent 進(jìn)行處理吉嚣,最后父 View調(diào)用onTouchEvent 方法梢薪,來(lái)處理這個(gè)事件。

5尝哆、如果 View 不消耗 ACTION_DOWN 以外的其他事件秉撇,那么這個(gè)點(diǎn)擊事件就會(huì)消失,此時(shí)的父元素的 onTouchEvent 不會(huì)調(diào)用秋泄,而且當(dāng)前 View 可以持續(xù)收到后續(xù)的事件琐馆,最終這些消失的事件會(huì)交給 Activity 處理。

6恒序、ViewGroup 默認(rèn)不攔截任何事件瘦麸,即 onInterceptTouchEvent 返回 false。

7歧胁、View 沒(méi)有 onInterceptTouchEvent滋饲。事件傳遞給它,ouTouchEvenr 就會(huì)調(diào)用喊巍。

8屠缭、View 的 ouTouchEvent 會(huì)默認(rèn)消耗事件 返回true。除非他是不可點(diǎn)擊 clickable 和longclickable 同時(shí)是 false崭参。longclickable默認(rèn)為false呵曹,button 默認(rèn) clickable 是true,textview 默認(rèn) false。

9奄喂、View 的enable 屬性不影響 ouTouchEvent 的返回值之剧,只要longclickable 或 clickable 有一個(gè)是 true ,那么ouTouchEvent 就返回 true砍聊。

10背稼、onClick 會(huì)發(fā)生的前提是 view 可點(diǎn)擊。并過(guò)去收到了down 和up玻蝌。

11蟹肘、事件傳遞是從外向內(nèi)的,事件總是先傳遞給父view俯树,然后再有父view分發(fā)給子view帘腹,通過(guò) requestDisallowInterceptTouchEvent 方法,子類(lèi)可以干預(yù)父 View 的事件分發(fā)過(guò)程许饿。ACTION_DOWN 除外阳欲。

12、mOnTouchListener > onTouchEvent > setOnClickListener

參考 《Android開(kāi)發(fā)藝術(shù)探索》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末陋率,一起剝皮案震驚了整個(gè)濱河市球化,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓦糟,老刑警劉巖筒愚,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異菩浙,居然都是意外死亡巢掺,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)劲蜻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)陆淀,“玉大人,你說(shuō)我怎么就攤上這事先嬉≡唬” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵坝初,是天一觀的道長(zhǎng)浸剩。 經(jīng)常有香客問(wèn)我钾军,道長(zhǎng)鳄袍,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任吏恭,我火速辦了婚禮拗小,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘樱哼。我一直安慰自己哀九,他們只是感情好剿配,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著阅束,像睡著了一般呼胚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上息裸,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天蝇更,我揣著相機(jī)與錄音,去河邊找鬼呼盆。 笑死年扩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的访圃。 我是一名探鬼主播厨幻,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腿时!你這毒婦竟也來(lái)了况脆?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤批糟,失蹤者是張志新(化名)和其女友劉穎漠另,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體跃赚,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笆搓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了纬傲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片满败。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖叹括,靈堂內(nèi)的尸體忽然破棺而出算墨,到底是詐尸還是另有隱情,我是刑警寧澤汁雷,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布净嘀,位于F島的核電站,受9級(jí)特大地震影響侠讯,放射性物質(zhì)發(fā)生泄漏挖藏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一厢漩、第九天 我趴在偏房一處隱蔽的房頂上張望膜眠。 院中可真熱鬧,春花似錦、人聲如沸宵膨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辟躏。三九已至谷扣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捎琐,已是汗流浹背抑钟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留野哭,地道東北人在塔。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像拨黔,于是被迫代替她去往敵國(guó)和親蛔溃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容