View的事件分发机制源码分析

View的事件分发机制

1. 基本的一些概念

1. MotionEvent

Java是面向对象的编程语言, 所以它把用户和屏幕的一次交互封装成了一个对象,这个就是MotionEvent
而我们对屏幕的交互一般来说,不外乎这么三个动作:
- 按下
- 移动
- 抬起

这三个动作分别对应了MotionEvent中的三个int值
- ACTION_DOWN 对应了我们手指刚按下的动作
- ACTION_MOVE 对应了我们手指在屏幕上移动的动作
- ACTION_UP 对应了我们手指从屏幕上抬起的动作

而一次交互,会产生至少一个ACTION_DOWN事件,若干个连续的ACTION_MOVE事件,和最多一个ACTION_UP事件.一次交互产生的一系列事件是属于同一个事件序列

我们讲的事件分发机制,就是指的经过一系列调用后,由谁来响应这一系列事件. 分发的开始是发送一个事件ACTION_DOWN.

2. public boolean dispatchTouchEvent(MotionEvent even)

如果事件被传递给了一个view,那么一定会调用到这个方法,它的返回值会由view中的onInterceptTouchEvent这个方法和下一级View的dispatchTouchEvent方法来决定,

具体来说就是一个if else条件判断,

先判断当前view的onInterceptTouchEvent方法的返回值,
如果为true,那么就表示拦截了这个事件:意思是这个事件其他的view就不用管了, 我来处理就好. 然后就会将MOtionEvent传递给当前view处理机制
否则就调用下一级view的dispatchTouchEvent方法,并将下一级View的该方法返回值返回.

View有这个方法。

3. public boolean onInterceptTouchEvent(MotionEvent even)

这个方法的返回值表示,传递给当前view的方法是不是要拦截下来,由自己来处理,返回true的话,表示消费了这个事件:意思是这个事件其他的view就不用管了, 我来处理就好.

注意:View没有这个方法,只有ViewGroup有这个方法,且默认返回false,即不拦截事件。

很好理解,只有ViewGroup才有子View,需要判断是否拦截发给子View的事件来交给自身处理。而普通的View不需要没有子View,不需要拦截事件,当事件被分发给一个普通View时,它直接处理即可

4. public boolean onTouchEvent(MotionEvent even)

这个方法会用来处理传递给当前view的事件,

返回true就表示消耗了这个事件,那么后续的一系列事件就会直接交给它来处理.

返回false,那么同一个事件序列中后续事件就不会受到,然后会把事件交给它的上一级去处理。

View的onTouchEvent默认都会返回true,表示消费了事件。除非他是不可点击的,即clickable和longclickable都为false。

2. 收到事件后的处理

而当MotionEvent被传递给当前view的处理机制后, 那么大致又会进行这样一个调用流程

1. 先判断view是否设置了onTouchListener

如果设置了的话,就会调用回调listener中的onTouch方法,然后返回一个布尔值.
否则就直接调用onTouchEvent()

2. 接下来就会判断上一步中的返回值,

如果是false,那么就会调用onTouchEvent方法.

在onTouchEvent方法中,如果一个View设置了onTouchListener。当点击这个View时,onTouchListener.onClick方法将会被调用

否则就不会onTouchEvent()方法

3. 源码分析:

1.1 事件的开始是从Activity的dispatchTouchEvevnt开始的

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

getWindow()获得的是一个Window对象,Window是一个抽象类,是对窗口这个概念的抽象。它的具体实现是PhoneWindow。我们查看PhoneWindow中的对应方法。

1.2 PhoneWindow.superDispatchGenericMotionEvent

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

其中,mDecor就是DecorView,是View的最顶层View。

    private DecorView mDecor;

    //通过调用getDecorView就可以获得当前PhoneWindow对应的DecorView
    @Override
    public final View getDecorView() {
        if (mDecor == null) {
            installDecor();
        }
        return mDecor;
    }

1.3 PhoneWindow.installDecor()

 private void installDecor() {
        if (mDecor == null) {
        //创建DecorView
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        ...
 }

  protected DecorView generateDecor() {
      return new DecorView(getContext(), -1);
  }

可以看到,我们最后直接new了一个DecorView对象。

1.4 DecorView.superDispatchTouchEvent

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

这个方法中,我们看到了熟悉的dispatchTouchEvent。

DecorView 是一个 ViewGroup,继承自 FrameLayout。所以调用了 FrameLayout 的 dispatchTouchEvent 方法。而DecorView是顶层的ViewGroup,它调用dispatchTouchEvent分发事件,而ViewGroup默认不拦截事件,那么事件就会一层层被传递到最底层的普通View。 接下来我们看看具体的 dispatchTouchEvent

2.1 FrameLayout .dispatchTouchEvent

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
      //触摸事件连贯性检测
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
      //事件安全过滤
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

          //当事件为DOWN事件时,对事件进行初始话。将mFirstTouchTarget置为null,并将FLAG_DISALLOW_INTERCEPT进行重置
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            //判断是否需要调用onInterceptTouchEvent
            final boolean intercepted;
          //(1)(1)(1) 标记,后面讲解会用到
          //如果事件是DOWN事件,那么进入if。或者事件不是DOWN事件,mFirstTouchTarget != null,说明子View消费了事件,也进入if
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                /*
              1. 判断FLAG_DISALLOW_INTERCEPT这个flag,这个flag在子view中可以调用requestDisallowInterceptTouchEvent来设置。 
              2.而这个flag在事件为Down事件时,会被进行重置。
              3. 这个flag可以使的父View无法拦截除了DOWN事件以外的其他事件
              */
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
              /*判断了flag,
              1. 事件为DOWN事件,那么一定能进入这个if判断。
              2. 如果事件不是DOWN事件,那么只有当子View没有调用requestDisallowInterceptTouchEvent,才会进入if。
              */
                if (!disallowIntercept) {
                  //调用onInterceptTouchEvent来判断是否拦截事件,ViewGroup默认返回flase
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was chnged
                } else {
                  /*if中条件不满足,intercepted为false。表示 
                  1. 事件一定不是DOWN事件并且mFirstTouchTarget != null。
                  2. 子View调用了requestDisallowInterceptTouchEvent,使得父View不能调用onInterceptTouchEvent来拦截事件。
                  */
                    intercepted = false;
                }
            } else {
              //当事件不为DOWN事件并且mFirstTouchTarget为null时。表示子View没有消费事件。那么后续的一系列事件都不会交给子View了
                intercepted = true;
            }

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

            // 事件是否取消检查
            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;
          //(2)(2)(2)  标记,后面讲解会用到
          //事件没有取消,并且intercepted值为flase才进入if
            if (!canceled && !intercepted) {
                  ...
                    //子View的数目
                    final int childrenCount = mChildrenCount;

                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        //判断子View中哪些是能接到点击事件的,即只有包含了事件发生位置的view才能接收到事件
                        final ArrayList preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                      //遍历子View
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, 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) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                          //将事件分发给子View,dispatchTransformedTouchEvent。具体解析看2.2小节
                            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();
                              //当dispatchTransformedTouchEvent返回值为true时,完成对mFirstTouchTarget的赋值。TouchTarget实际上是一个链表数据结构
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                  ...
                    }
                }
            }
           /*
           1.(1)标记处最后intercepted为flase,那么会进入(2)号标记的if中将事件分发给子View,如果子View最终消费了事件,返回了true,那么mFirstTouchTarget.child会被指向子View,mFirstTouchTarget就不会为null。那么当前这个if判断就进不去
            2. 而若  mFirstTouchTarget为null, 说明在DOWN事件中,要么父View调用onInterceptTouchEvent拦截了事件,要么子View没有没有消费DOWN事件。
            3. 当子View没有消费事件,则后续一系列的事件都不会交给子View来处理了。
            4. 若父View拦截了DOWN事件,并消费处理事件,那么后续连续事件的mFirstTouchTarget.child就会指向这个View。最后还是会调用mFirstTouchTarget.child.onTouchEvent来让父View处理。
           */

            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
              //调用具体解析看2.2小节
                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;
                }
            }
            ...
        }

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

2.2 ViewGroup.dispatchTransformedTouchEvent

// 这个方法用来进行过滤,根据参数中child是否为null,来判断是将事件交给子View处理,还是交给父View处理。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        final int oldAction = event.getAction();
        //判断是否有特殊的取消指令,有的话将事件都转为CANCEL事件
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            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;

        //特殊情况下指针数量为0,产生了一个没有指针的Action。那么就丢弃这个事件
        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()) {
                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);
        }

        //当child属性为null,那么调用当前View的父View中的dispatchTouchEvent。最后会调到ViewGroup的父View中的dispatchTouchEvent。 而ViewGroup的父View就是View这个类。实际上最后会调用onTouchEvent方法。
        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());
            }
            /*child不为空的话,就会调用child的dispatchTouchEvent方法。而当一个底层View消费了事件之后。这个child就会指向它。 所以会直接调用底层View的dispatchTouchEvent。而底层View的dispatchTouchEvent会直接调用到onTouchEvent。
          最后将事件分发完毕。
          具体解析看2.3小节
          */
            handled = child.dispatchTouchEvent(transformedEvent);
        }

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

可以看到dispatchTransformedTouchEvent会根据第三个参数child来进行判断。

当child不为null,则调用child的dispatchTouchEvent。

当child为null,则调用viewGroup的父类的dispatchTouchEvent,而viewGroup的父类是View。即调用View.dispatchTouchEvent。

2.3 View.dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
        ...
        //事件安全性判断
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
          //判断如果设置了OnTouchListener,那么会调用OnTouchListener.onTouch()并判断返回值。如果返回值为true,那么将result变量设置为true。否则result值为false
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //如果OnTouchListener.onTouch()的返回值为false,才会调用onTouchEvent。onTouchEvent返回值为true,则将result置为true
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        ...

        return result;
    }

可以看出,当一个view处理事件时,会先判断这个view的onTouchListener这个回调是否设置,如果设置了这个回调,那么就会调用OnTouchListener.onTouch()。onTouch的返回值为true的情况下,就不会调用onTouchEvent。

只有onTouch返回值为false的情况下,才会调用onTouchEvent。

View.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是否是Enable的。即便一个view处于disable的状态下,也是会消费事件的
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
        }
        //判断是否设置了代理,如果设置了代理,将事件传递给代理view去处理。例如
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //当viewCLICKABLE、LONG_CLICKABLE、CONTEXT_CLICKABLE有一个为true时,才能处理事件。
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                //UP事件的处理
                case MotionEvent.ACTION_UP:
                    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
                            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.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                  //判断一个事件为点击事件后,如果设置了onCLickListener,那么performClick方法会调用onCLickListener的onClick方法
                                    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;
                //DOWN事件的处理
                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;

                    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:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                //MOVE事件的处理
                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);

                    // Be lenient about moving outside of buttons
                    if (!pointInView(x, y, mTouchSlop)) {
                        // Outside button
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            // Remove any future long press/tap checks
                            removeLongPressCallback();

                            setPressed(false);
                        }
                    }
                    break;
            }
            //默认返回true
            return true;
        }

        return false;
    }

你可能感兴趣的:(android源码)