dispatchTouchEvent源码分析

简介

通过对dispatchTouchEvent事件分发的理解,了解android事件的处理机制


事件分发流程

首先先确认事件由系统传递给当前Activity,然后由Activity开始分发,主要的流程:

Activity -> PhoneWindow -> DecorView -> ViewGroup -> … -> View

看一下Activity.dispatchTouchEvent()源码

    /**
     * Called to process touch screen events.  You can override this to
     * intercept all touch screen events before they are dispatched to the
     * window.  Be sure to call this implementation for touch screen events
     * that should be handled normally.
     *
     * @param ev The touch screen event.
     *
     * @return boolean Return true if this event was consumed.(如果这个事件被消耗,返回true)
     */
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 空方法
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

源码中可以看到,在Activity dispatchTouchEvent中,调用了Window的superDispatchTouchEvent,如果getWindow().superDispatchTouchEvent(ev)返回true,则这个事件被消耗,否则调用Activity的onTouchEvent方法,看一下Window是如何分发事件的:

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

Window调用了mDecor的superDispatchTouchEvent。mDecor是什么???

  // This is the top-level view of the window, containing the window decor.(这是窗口的顶层视图)
    private DecorView mDecor;

    private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

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

调用了FrameLayout 的dispatchTouchEvent,这里说一下DecorView 是什么,DecorView继承自FrameLayout它是整个界面的最外层的ViewGroup。 也就是说整个Activity的根布局外面还包了一层DecorView,我们手机的标题栏就是显示在DecorView中。至此,Touch事件就已经由Activity到了顶层的View。继续看一下DecorView 是怎么分发的:

    /**
     * {@inheritDoc}
     */
    @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;

            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); 
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }

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

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

            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;

                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                       //计算Touch事件的坐标
                        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;
                        //判断哪个子View接收Touch事件
                        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) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            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();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            if (mFirstTouchTarget == null) {
                // 第三部分(child view不能消耗事件,把自己当做view来消耗)
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
            } else {

                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 (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;
    }

代码有点多,我简单的分成了四部分来分析:

第一部分:

   private void cancelAndClearTouchTargets(MotionEvent event) {
       ...
       clearTouchTargets();
       ...
    }

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

当ACTION_DOWN时进行初始化和还原操作。在cancelAndClearTouchTargets( )中将mFirstTouchTarget设置为null,且在resetTouchState()中重置Touch状态标识

第二部分:

    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 {
        // There are no touch targets and this action is not an initial down
        // so this view group continues to intercept touches.
        intercepted = true;
    }

检查是否需要ViewGroup拦截Touch事件

先看一下最外面的判断条件:

actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null

ACTION_DOWN 很好理解,mFirstTouchTarget 是什么??mFirstTouchTarget 是TouchTarget类的对象,而TouchTarget是ViewGroup中的一个内部类,它封装了被触摸的View及这次触摸所对应的ID,mFirstTouchTarget 在后面还有分析,这里因为有用到,先说下FirstTouchTarget的作用

(1) mFirstTouchTarget!= null
表示ViewGroup没有拦截Touch事件并且子View消费了Touch

(2) mFirstTouchTarget == null
表示ViewGroup拦截了Touch事件或者虽然ViewGroup没有拦截Touch事件但是子View也没有消费Touch。总之,此时需要ViewGroup自身处理Touch事件

也就是说如果child view没有消费ACTION_DOWN ,之后的move和up就不会往下传递,会被parent view拦截下来。事件被拦截下来会做些什么,没有拦截又会做些什么??

我们先从intercepted = false 没有拦截来看一下:

if (!canceled && !intercepted) {

    ...

    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // Child wants to receive touch within its bounds.
        newTouchTarget = addTouchTarget(child, idBitsToAssign);
        alreadyDispatchedToNewTouchTarget = true;
        break;
    }

    ....

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

      if (child == null) {
            handled = super.dispatchTouchEvent(event);
      } else {
            handled = child.dispatchTouchEvent(event);
      }

    }
    ...

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

在这个步骤中只有找到了可以消费Touch事件的子View时mFirstTouchTarget才不为null;其余情况比如未找到可以接收Touch事件的子View或者子View不能消费Touch事件时mFirstTouchTarget仍为null

如果Touch事件没有被取消也没有被拦截,那么ViewGroup将类型为ACTION_DOWN的Touch事件分发给child View。

(1) child == null

ViewGroup虽然override了dispatchTouchEvent方法,但是ViewGroup的dispatchTouchEvent是没有super父类的,如果child == null,就把ViewGroup当做一个普通的View处理

(2) child != null
调用child.dispatchTouchEvent继续分发


intercepted = true 拦截

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

...

if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
     handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
}


...

因为intercepted = true, if (!canceled && !intercepted) { … } 都不会执行,所以mFirstTouchTarget == null,child ==null ,把ViewGroup当做普通的View处理,(普通的View的dispatchTouchEvent就是调用onTouchEvent())

View的dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
    //noinspection SimplifiableIfStatement
     ListenerInfo li = mListenerInfo;
     if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
     }

     if (!result && onTouchEvent(event)) {
        result = true;
      }
   }
}

如果onTouchEvent 返回false,那么mFirstTouchTarget就为null,就会把ViewGroup当做普通View调用dispatchTouchEvent(onTouchEvent),这也就是平时所说的如果child view消耗不掉这个事件,会返回给parent的onTouchEvent中消耗,parent view也消耗不了,继续往上传,直到传递到Activity的dispatchTouchEvent中,不过这时getWindow().superDispatchTouchEvent = false,会调用Activity的onTouchEvent。


总结

通俗语言总结一下,事件来的时候,Activity会询问Window,Window这个事件你能不能消耗,Window一看,你先等等,我去问问DecorView他能不能消耗,DecorView一看,onInterceptTouchEvent返回false啊,不让我拦截啊,遍历一下子View吧,问问他们能不能消耗,那个谁,事件按在你的身上了,你看看你能不能消耗,RelativeLayout一看,也没有让我拦截啊,我也得遍历看看这个事件发生在那个子View上面,那个TextView,事件在你身上,你能不能消耗了他。TextView一看,消耗不了啊,RelativeLayout一看TextView消耗不了啊,mFirstTouchTarget==null啊,得,我自己消耗吧,嗯!一看自己的onTouchEvent也消耗不了啊!那个DecorView事件我消耗不了,DecorView一看自己,我也消耗不了,继续往上传,那个Window啊。事件我消耗不了啊,Window再告诉Activity事件消耗不了啊。Activity还得我自己来啊。调用自己的onTouchEvent,还是消耗不了,算了,不要了。

伪代码

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean result = false;             // 默认状态为没有消费过

    if (!onInterceptTouchEvent(ev)) {   // 如果没有拦截交给子View
        result = child.dispatchTouchEvent(ev);
    }

    if (!result) {                      // 如果事件没有被消费,询问自身onTouchEvent
        result = onTouchEvent(ev);
    }

    return result;
}

如果子View连ACTION_DOWN都不能消耗掉 ,则mFirstTouchTarget == null,那么后续的ACTION_UP,ACTION_MOVE子View也不会再收到


猜你喜欢

转载自blog.csdn.net/fuxiaoyuqing/article/details/79317120
今日推荐