dispatchTouchEvent源码分析

来源:互联网 发布:战略岗位 知乎 编辑:程序博客网 时间:2024/05/16 17:24

简介

通过对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也不会再收到

0 0