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

来源:互联网 发布:猎上网怎么样知乎 编辑:程序博客网 时间:2024/05/18 02:00

背景:研究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在适当的时间取消通知。
*
* <p>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。
*
* <p>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.
*
* <p>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 booleandisallowIntercept = (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()来拦截事件
*
* <p>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.</p>
  父控件及其祖父空间必须遵守子控件的请求,知道调用了up、cancel时取消。
*
* @param disallowIntercept True if the child does not want the parent to
*            intercept touch events.
*/
public voidrequestDisallowInterceptTouchEvent(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.
实现该方法可以拦截所有的触屏事件。
*
* <p>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:
*
* <ol>
* <li> You will receive the down event here.
* <li> 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()方法来处理。


* <li> 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().
* <li> 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.
* </ol>

*
* @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<View> 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一张图总结下。
原创粉丝点击