Android 事件分发机制---Down事件源码理解


背景:研究Android的事件分发机制是如何实现的。

一、事件分发用到的方法:
1、public boolean dispatchTouchEvent(MotionEvent ev):     用于TouchEvent事件的分发;
2、public boolean onInterceptTouchEvent(MotionEvent ev):事件拦截;
3、boolean onTouch(View v, MotionEvent event):                事件的回调;
4、public boolean onTouchEvent(MotionEvent event):         用来处理事件


二、点击事件分发流程:先基于MotionEvent.ACTION_DOWN来研究

1、首先,当一个手机屏幕上的点击事件产生后,事件会先传递给当前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.
  触摸屏幕事件的过程中被调用。可以通过复写该方法来拦截所有的触摸屏幕事件,在这些事件被传递给window之前。
  为了这些被拦截的触摸屏幕事件能够被正常的处理,所以一定要调用该方法的实现方法。
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();//-----001 与用户交互
    }
    if (getWindow().superDispatchTouchEvent(ev)) {//------002
        return true;
    }
    return onTouchEvent(ev);
}


(001)onUserInteraction() 处源码解读
/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity.  Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
  当一个key、touch、或者trackball事件被分发给Activity,该方法则会被调用。可以实现该方法,如果你想知道
  用户与设备间以某种方式交互,当该Activity正在运行。这个回调和onUserLeaveHint()方法旨在帮助Activities
  智能的管理状态栏通知;帮助Activity在适当的时间取消通知。
*
*

All calls to your activity's {@link #onUserLeaveHint} callback will

* be accompanied by calls to {@link #onUserInteraction}.  This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
  所有Activity的onUserLeaveHint()回调方法的调用,都伴随着该方法的调用。这样做能够确保你的
  Activity被告知相关的用户操作活动,例如拉下了通知栏面板并且点击一个item。
*
*

Note that this callback will be invoked for the touch down action

* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
  注意,这个方法的回调只在触摸手势的Down操作,Move和Up操作不会被调用。
*
* @see #onUserLeaveHint()
*/
public void onUserInteraction() {
}

(002)getWindow().superDispatchTouchEvent(ev)源码解读:
1、getWindow():获取一个PhoneWindow的实现对象
/**
* Abstract base class for a top-level window look and behavior policy.  An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
*

The only existing implementation of this abstract class is

* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
PhoneWindow是该抽象类的唯一实现类
*/
public abstract class Window {}


2、查看PhoneWindow.superDispatchTouchEvent(ev)方法实现:调用了DecorView的superDispatchTouchEvent方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

3、查看DecorView方法实现:调用了super方法
public boolean superDispatchTrackballEvent(MotionEvent event) {
    return super.dispatchTrackballEvent(event);
}

4、DecorView继承了FrameLayout,而FrameLayout继承了ViewGroup,最终调用了ViewGroup的dispatchTouchEvent方法。

总结:如果Activity复写了dispatchTouchEvent()方法,并且return true/false,则Activity自己消费了触摸事件,不再分发;
只有return super.diapatchTouchEvent()方法,才能使得Activity将事件进行自上而下的分发。


2、ViewGroup的dispatchTouchEvent()方法分段解析:
1.清空之前的指令:
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.
    cancelAndClearTouchTargets(ev);
    resetTouchState();//----每次的Down事件,都会将之前的mFirstTouchTarget值设置为null
}


2.校验是否拦截:
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {

    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//-----003子控件是否设置拦截

    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);//------004
        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;
}

1.if语句进入的条件:Down事件、或者mFirstTouchTarget !=null,一般事件分发到ViewGroup时,mFirstTouchTarget==null;
2.boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;FLAG_DISALLOW_INTERCEPT是标记位,
一般是由ViewGroup的子控件通过复写requestDisallowInterceptTouchEvent(boolean)方法设置的。如果子控件设置true则为true,
如果没有设置则为false。
3.if语句则会执行onInterceptTouchEvent(),进行事件拦截;方法执行结果为 intercepted = false

(003)boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;源码解读
1、ViewGroup实现了接口ViewParent
/**
* Called when a child does not want this parent and its ancestors to
* intercept touch events with
* {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
  当子控件不希望父控件及其祖父控件通过onInterceptTouchEvent()来拦截事件
*
*

This parent should pass this call onto its parents. This parent must obey

* this request for the duration of the touch (that is, only clear the flag
* after this parent has received an up or a cancel.

  父控件及其祖父空间必须遵守子控件的请求,知道调用了up、cancel时取消。
*
* @param disallowIntercept True if the child does not want the parent to
*            intercept touch events.
*/
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);
小结:当子控件设置了 requestDisallowInterceptTouchEvent(true),那么disallowIntercept=true,那么就不会进入if语句,也就不会执行 onInterceptTouchEvent(ev);方法。如果子控件未设置 requestDisallowInterceptTouchEvent(),那么disallowIntercept默认为false,则会执行onInterceptTouchEvent(ev);方法。
因此,dispatchTouchEvent()方法return super时默认会执行onInterceptTouchEvent()方法。

(004) onInterceptTouchEvent(ev);源码解读
/**
* Implement this method to intercept all touch screen motion events.  This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
实现该方法可以拦截所有的触屏事件。
*
*

Using this function takes some care, as it has a fairly complicated

* interaction with {@link View#onTouchEvent(MotionEvent)
* View.onTouchEvent(MotionEvent)}, and using it requires implementing
* that method as well as this one in the correct way.  Events will be
* received in the following order:
*
*
*
  • You will receive the down event here.
  • *
  • The down event will be handled either by a child of this view
  • * group, or given to your own onTouchEvent() method to handle; this means
    * you should implement onTouchEvent() to return true, so you will
    * continue to see the rest of the gesture (instead of looking for
    * a parent view to handle it).  Also, by returning true from
    * onTouchEvent(), you will not receive any following
    * events in onInterceptTouchEvent() and all touch processing must
    * happen in onTouchEvent() like normal.
    Down事件你可以交给自己的子类去处理,也可以交给自己的onTouchEvent()方法处理。当自己的onTouchEvent()方法return true时,
    将会接受到接下来的move/up等事件,而不是交由父类来处理。同样的,onInterceptTouchEvent()方法将不会接收到move/up事件,
    而是直接交给onTouchEvent()方法来处理。


    *
  • For as long as you return false from this function, each following
  • * event (up to and including the final up) will be delivered first here
    * and then to the target's onTouchEvent().
    *
  • If you return true from here, you will not receive any
  • * following events: the target view will receive the same event but
    * with the action {@link MotionEvent#ACTION_CANCEL}, and all further
    * events will be delivered to your onTouchEvent() method and no longer
    * appear here.
    *

    *
    * @param ev The motion event being dispatched down the hierarchy.
    * @return Return true to steal motion events from the children and have
    * them dispatched to this ViewGroup through onTouchEvent().
    * The current target will receive an ACTION_CANCEL event, and no further
    * messages will be delivered here.
    */
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

    1. InputDevice.SOURCE_MOUSE:输入源是鼠标的定位设备;
    2. MotionEvent.BUTTON_PRIMARY:主要按钮,鼠标左键;
    3. isOnScrollbarThumb(ev.getX(), ev.getY()):在滚动滚动条;
    默认该方法return false,不进行拦截。

    小结:onInterceptTouchEvent()方法return true时,Down事件将被拦截后直接交给自己的onTouchEvent处理,onTouchEvent方法return true时,那么后续的move、up事件将由该级的ViewGroup直接分发给onTouchEvent方法处理,不再经过onInterceptTouchEvent方法。如果onTouchEvent()方法return false时,那么Down事件将会被向上传给父类的onTouchEvent()方法处理,那么后续的move、up事件将不会经过该级别的viewGroup的dispatchTouchEvent()方法啦。

    2、结合源码分析,为什么子控件设置requestDisallowInterceptTouchEvent可以影响父控件的拦截事件
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
            // We're already in this state, assume our ancestors are too
            return;//--------A
        }

        if (disallowIntercept) {//-------B
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }

        // Pass it up to our parent
        if (mParent != null) {
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

    1).假设(mGroupFlags & FLAG_DISALLOW_INTERCEPT)==0;
       子控件设置了disallowIntercept = true拦截;则会执行到 B 代码处;
    mGroupFlags |= FLAG_DISALLOW_INTERCEP ==> mGroupFlags = mGroupFlags|FLAG_DISALLOW_INTERCEP;在判断当前父控件
    是否还有父控件,有的话则一直请求父控件放行除了Down事件。
    再看:父控件执行dispatchTouchEvent()方法中的boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    此时的disallowIntercept = (mGroupFlags | FLAG_DISALLOW_INTERCEP & FLAG_DISALLOW_INTERCEPT)!=0; 
    最终简化成判断 disallowIntercept = FDI !=0 为true。
    2).假设(mGroupFlags & FLAG_DISALLOW_INTERCEPT)!=0;
       子控件设置了disallowIntercept = true拦截;则会执行到 A代码处;则直接返回,表明父控件及其祖父控件disallowIntercept值为true。
    所以,最终的disallowIntercept值仍为 true.

    3、再看父控件的dispatchTouchEvent()方法:
    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;//--------C
    }
    由于子控件设置了requestDisallowInterceptTouchEvent(true),则disallowIntercept = true;
    通过if语句执行后到 C代码处,intercepted = false;表明父控件不拦截,不调用onInterceptTouchEvent()方法。

    2、事件分发
    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) {
                final float x = ev.getX(actionIndex);
                final float y = ev.getY(actionIndex);

                final ArrayList preorderedList = buildTouchDispatchChildList();
                final boolean customOrder = preorderedList == null
                        && isChildrenDrawingOrderEnabled();
                final View[] children = mChildren;

                for (int i = childrenCount - 1; i >= 0; i--) {//----------A 倒序遍历
                    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)) {//---------D
                        ev.setTargetAccessibilityFocus(false);
                        continue;
                    }

                    newTouchTarget = getTouchTarget(child);
                    if (newTouchTarget != null) {
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                        break;
                    }

                    resetCancelNextUpFlag(child);
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {//--------B
                        // 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);//-------C
                        alreadyDispatchedToNewTouchTarget = true;//记录Down事件被消费了
                        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;
            }
        }
    }

    1.A 代码处的for循环,是对所有的子View进行遍历,是从最上层到内层的遍历,自上而下的遍历过程,默认是不拦截事件的;

    2.B 处代码是将事件分发给子类处理,dispatchTransformedTouchEvent()方法过长,可用如下伪代码讲解:也就是传入的child是否为null;
    有子View,就由子View再进行事件分发;如果没有子View,在调用父类的事件分发方法。这里也只有包含触摸点的子View才能达到。
    举例:如果一个ViewGroup下有View1和View2.当在View2上点击是,只有View2才能继续Down事件分发,View1会在D处被直接过滤掉。
    if (child == null) {
        handled = super.dispatchTouchEvent(event);//------006
    } else {
        handled = child.dispatchTouchEvent(event);//------005
    }
    return handled;


    (005、006)handled = child.dispatchTouchEvent(event);代码讲解
    下面对View.dispatchTouchEvent()代码进行缩减处理:
    public boolean dispatchTouchEvent(MotionEvent event) {

        boolean result = false;
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null&& (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) { //-----1
                 result = true;
            }
          
            if (!result && onTouchEvent(event)) {//-----2
                result = true;
            }
        return result;
    }
    包含触摸点的ViewGroup,会执行dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法,也就是上面的 B 处代码,会将Down事件交由子View处理,如果子View中复写了dispatchTouchEvent()方法,return true表明Down事件由自己消费,那么上面就会执行C处代码,找到了TargetView;return false则会继续执行for循环;return super的话,则会执行View.dispatchTouchEvent()方法,也就是上面的伪代码。可以看见在1、2处,如果在最底层的子View中设置了onTouchListen.onTouch()方法,并且返回值为true时,则不会执行onTouchEvent()方法,并且dispatchTouchEvent()方法返回true。
    小结:如果包含触摸点的最底层View,retrun true自己消费Down事件,不会执行onTouchEvent()方法;return false也不会执行onTouchEvent()方法,并交给ViewGroup处理,也就是上面 B处代码;retun super时如果有设置onTouchListener.onTouch()方法,那么会优先执行OnTouch()方法,并且其返回值会影响是否执行onTouchEvent()方法。

    子View在处理onTouchEvent()事件时,如果子View是Clickable默认为true的话,那么及时不做处理,也会返回true。也就是说,最底层的控件如果继承TextView的话,
    那么默认的执行onTouchEvent()返回true,如果继承View的话,则默认返回false。
    onTouchEvent()源码:
    if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
            (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {  
        return true;
    }
    return false;

    3.如果B 处代码返回false,则表示子View未进行消费,那么则继续for遍历其他子View直到结束。mFirstTouchTarget和newTouchTarget都为null。
    如果返回true,那么表示有子View对Down事件消费了,到C代码;
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

    4.C 处代码,主要是将mFirstTouchTarget指向了目标View。同时将newTouchTarget也指向了目标View,然后跳出了for循环。
    5.D 处代码,用来判断ViewGroup下的所有子View,是否包含触摸点位置,如果没有则continue下一个View。也就是如果ViewGroup下的子View不包含触摸点,那么直接跳过,不交由它处理下发的事件。最终实现的,其实就是从上至下的一条直达目标的捷径路线,不会有多余的操作。
    小结:事件分发总是从上而下的,默认是不拦截的;如果ViewGroup下还有多个ViewGroup,每个二级ViewGroup下有多个子View,那么此时事件分发:有顶级ViewGroup分发给二级ViewGroup,判断每一个二级ViewGroup是否包含触摸点,不包含直接跳过;直接找到包含触摸点的二级ViewGroup;同样的再下发给三级View的时候,也是只有包含触摸点的子View才有
    机会继续下发Down事件,其它子View会被直接continue过滤掉。这是由 D 处代码带来的效果。


    3、事件分发分段代码
    Down事件继续解析:
    if (mFirstTouchTarget == null) {
        // No touch targets so treat this as an ordinary view.
        handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);//---------A 执行到这,表明没有子View消费
    } else {
        // Dispatch to touch targets, excluding the new touch target if we already
        // dispatched to it.  Cancel touch targets if necessary.
    执行到这,表明有子View消费了Down事件
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            final TouchTarget next = target.next;
            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {//------B
                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;
        }
    }

    return handled;

    1.没有消费:当最低层的ViewGroup,将Down事件分发给包含触摸点的子View,并且子View没有消费Down事件,那么就会执行到 A 处代码。A 处代码里面,也就是上文的006处的代码,因为这个时候child传入的是null;就会执行super.dispatchTouchEvent()方法,child的super其实也就是自己,也就是上面的005、006处伪代码,也就会执行自己的onTouch()、onTouchEvent()方法。最底层的ViewGroup如果设置了onTouchListener.onTouch()结果也会影响onTouchEvent()方法;如果onTouchEvent() return true则自己消费;return false则继续交由上一层ViewGroup处理;return super方法,那么也会父类的dispatchTouchEvent()方法。也就是向上执行onTouchEvent()方法。

    2、被消费了:那么就会执行到B处,handled =true,最后就会将true返回。

    总结:
    完整的Down事件分发流程:
    Activity将Down事件,return super最终交给顶层的ViewGroup处理,顶层ViewGroup通过dispatchTouchEvent()方法,遍历查找到包含触摸点的最底层ViewGroup、及View,将Down事件一级级下发。
    如果有某一级的ViewGroup的dispatchTouchEvent()方法return true不再向下分发,则由这一级ViewGroup消费了;return false不再向下分发,交由上一级ViewGroup执行onTouchEvent()方法;return super方法才会继续向下分发。
    最底层的View在执行dispatchTouchEvent()方法时,return true则自己消费,return false则交由上一级ViewGroup处理,这2中情况都不会执行自己的onTouchEvent()方法;return super时才会执行自己的onTouchEvent()方法,如果结果还是自己不消费,那么则会向上逐级的调用ViewGroup的onTouchEvent()方法。
    最终又会交由Activity的onTouchEvent()处理。向上逐级执行onTouchEvent()时,只要某一级return true,那么Down事件也就停止向上执行了。

    借用 http://www.jianshu.com/p/e99b5e8bd67b一张图总结下。

    你可能感兴趣的:(Android学习之路,android事件分发机制)