源码角度再看Android事件分发机制

基础了解

MotionEvent

所谓点击事件分发,其实就是对MotionEvent分发。当一个MotionEvent产生了以后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是分发过程。

三种主要事件Action


package android.view;

public final class MotionEvent extends InputEvent implements Parcelable {

    ...

    /**
     * Constant for {@link #getActionMasked}: A pressed gesture has started, the
     * motion contains the initial starting location.
     * <p>
     * This is also a good time to check the button state to distinguish
     * secondary and tertiary button clicks and handle them appropriately.
     * Use {@link #getButtonState} to retrieve the button state.
     * </p>
     * 手指刚接触屏幕
     */
    public static final int ACTION_DOWN             = 0;

    /**
     * Constant for {@link #getActionMasked}: A pressed gesture has finished, the
     * motion contains the final release location as well as any intermediate
     * points since the last down or move event.
     * 手指在屏幕上移动
     */
    public static final int ACTION_UP               = 1;

    /**
     * Constant for {@link #getActionMasked}: A change has happened during a
     * press gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}).
     * The motion contains the most recent point, as well as any intermediate
     * points since the last down or move event.
     * 手指从屏幕上松开的一瞬间
     */
    public static final int ACTION_MOVE             = 2;

    ...

}
  • 当手指点击屏幕后松开,事件序列为DOWN->UP
  • 当手指点击屏幕,滑动一会,再抬起手指离开屏幕,时间序列为DOWN->MOVE->..MOVE->UP

getX/getY | getRawX/getRawY

  • getX/getY 返回的是相当于当前View左上角的x和y坐标
  • getRawX/getRawY 返回的是相对于手机屏幕左上角的x和y坐标

三个方法了解一下

android.view.View # public boolean dispatchTouchEvent(MotionEvent event)

用来进行事件的分发。如果事件能够传递给当前View,那么此方法一定会被调用。

android.view.ViewGroup # public boolean onInterceptTouchEvent(MotionEvent ev)(MotionEvent ev)

在dispatchTouchEvent内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么同一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前事件(true表示拦截,false表示继续向下分发给它的子元素)

android.view.View # public boolean onTouchEvent(MotionEvent event)

还是在dispatchTouchEvent内部调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

一段经典的伪代码:


    public boolean dispatchTouchEvent(MotionEvent ev){  
        boolean consume = false;  
        if(onInterceptTouchEvent(ev)){  
            consume = onTouchEvent(ev);  
        } else {  
            consume = child.dispatchTouchEvent(ev);  
        }  

        return consume;  
    }  

从上面的代码可以看出,对于一个根ViewGroup,点击事件产生后,首先会传递给它,这时它的dispatchTouchEvent被调用,如果这个ViewGroup的onInterceptTouchEvent执行后返回结果,并根据这个结果进行不同方式的处理:
a.若返回结果为为true,则表示拦截,这时候根据代码显示就是终止对事件的分发并且自身调用onTouchEvent对本此事件进行处理。
b.若返回结果为false,根据代码显示,则会去调用child.dispatchTouchEvent(ev),也就是继续向下分发给自己的子View元素,再往后就是子元素去进行处理直到这个事件结束。 _

一些结论

  • (1)同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。

  • (2)正常情况下,一个事件序列只能被一个View拦截且消耗。这一条的原因可以参考(3),因为一旦一个元素拦截了某此事件,那么同一个事件序列内的所有事件都会直接交给它处理,因此同一个事件序列中的事件不能分别由两个View同时处理,但是通过特殊手段可以做到,比如一个View将本该自己处理的事件通过onTouchEvent强行传递给其他View处理。

  • (3)某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用。这条也很好理解,就是说当一个View决定拦截一个事件后,那么系统会把同一个事件序列内的其他方法都直接交给它来处理,因此就不用再调用这个View的onInterceptTouchEvent去询问它是否要拦截了。

  • (4)某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理,那么它就必须消耗掉,否则同一事件序列中剩下的事件就不再交给它来处理了,这就好比上级交给程序员一件事,如果这件事没有处理好,短期内上级就不敢再把事情交给这个程序员做了,二者是类似的道理。

  • (5)如果View不消耗除ACTION_DOWN以外的其他事件,那么这个点击事件会消失,此时父元素的onTouchEvent并不会被调用,并且当前View可以持续收到后续的事件,最终这些消失的点击事件会传递给Activity处理。

  • (6)ViewGroup默认不拦截任何事件。Android源码中ViewGroup的onInterceptTouch-Event方法默认返回false。

  • (7)View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。

  • (8)View的onTouchEvent默认都会消耗事件(返回true),除非它是不可点击的(clickable 和longClickable同时为false)。View的longClickable属性默认都为false,clickable属性要分情况,比如Button的clickable属性默认为true,而TextView的clickable属性默认为false。

  • (9)View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态的,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true。

  • (10)onClick会发生的前提是当前View是可点击的,并且它收到了down和up的事件。

  • (11)事件传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外。

深入了解

事件分发流程梳理1—从Activity到顶级ViewGroup(View)

点击事件最先传递给Activity,由Activity的dispatchTouchEvent来进行事件派发,具体工作是由Window来完成。先知道这么多,我们就以Activity作为入口,然后就边看代码变分析吧。

Activity#dispatchTouchEvent


    package android.app;

    public class Activity extends ContextThemeWrapper
            implements LayoutInflater.Factory2,
            Window.Callback, KeyEvent.Callback,
            OnCreateContextMenuListener, ComponentCallbacks2,
            Window.OnWindowDismissedCallback, WindowControllerCallback,
            AutofillManager.AutofillClient {

       ......

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

       ......
    }

这个方法其实跟本文主题无关,但看见了还是简单了解一下,一个判断,当事件DOWN来袭的时候。调用onUserInteraction(),首先它是一个空实现方法,这种方法一般是系统留给我们复写的接口方法。然后它跟另外一个方法有关系onUserLeaveHint,这俩都是在特定情境下可以复写的接口方法。

  • Activity#onUserInteraction()
    activity在分发各种事件的时候会调用该方法,注意:启动另一个activity,Activity#onUserInteraction()会被调用两次,一次是activity捕获到事件,另一次是调用Activity#onUserLeaveHint()之前会调用Activity#onUserInteraction()。

  • Activity#onUserLeaveHint()
    用户手动离开当前activity,会调用该方法,比如用户主动切换任务,短按home进入桌面等。系统自动切换activity不会调用此方法,如来电,灭屏等。

然后我们看到这一行:

   if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
   }

getWindow()返回一个Window,然后看Window下的superDispatchTouchEvent。

Window#superDispatchTouchEvent

package android.view;

/**
 * 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.
 */
public abstract class Window {

   ......

    /**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
     public abstract boolean superDispatchTouchEvent(MotionEvent event);
   ......
}

一个抽象方法,我们去找他的实现类。看顶上的注释:
The only existing implementation of this abstract class is android.view.PhoneWindow

PhoneWindow#superDispatchTouchEvent


package com.android.internal.policy

public class PhoneWindow extends Window implements MenuBuilder.Callback { 

 // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    ......

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

    ......

}

科普一波:
DecorView是啥?它一般是当前界面最底层的容器(即setContentView之后产生的View的父容器)。可以通过代码Activity$View decorView = getWindow().getDecorView(); 获得

好了,来嘛,继续看嘛,mDecor.superDispatchTouchEvent(event);看看DecorView嘛

PhoneWindow#superDispatchTouchEvent


package com.android.internal.policy

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {

 // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    ......

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

    ......

}

它调用了super.dispatchTouchEvent(event),看它的父类,这里DecorView extends FrameLayout,FrameLayout是个ViewGroup,本质上也是个View,所以到这儿,事件就从Activity传递到了ViewGroup层。

事件分发流程梳理2-ViewGroup层的事件下发流程

分发逻辑回顾

点击事件达到顶级View(一般是一个ViewGroup)以后,会调用ViewGroup的dispatchTouchEvent方法,然后的逻辑是这样的:

如果顶级ViewGroup拦截事件即onInterceptTouchEvent返回true,则事件由ViewGroup处理

  • 这时如果ViewGroup的mOnTouchListener被设置,则onTouch会被调用。
  • 否则onTouchEvent会被调用。在onTouchEvent中,如果设置了mOnClickListener,则onClick会被调用。
  • 如果都提供的话,onTouch会屏蔽掉onTouchEvent。

如果顶级ViewGroup不拦截事件,返回false

  • 则事件会传递给它所在的点击事件链上的子View,这时子View的dispatchTouchEvent会被调用。到此为止,事件已经从顶级View传递给了下一层View,接下来的传递过程和顶级View是一致的,如此循环,完成整个事件的分发。

ViewGroup—onInterceptTouchEvent调用流程分析(是否拦截事件)

在ViewGroup内搜到方法dispatchTouchEvent,然后ctrl+f // Check for interception.

   // Check for interception.
   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); // 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;
   }
  • actionMasked == MotionEvent.ACTION_DOWN
    当前事件是按下事件。
  • mFirstTouchTarget != null
    当事件由ViewGroup的子元素处理成功时,mFirstTouchTarget会被赋值指向子元素,于是(ViewGroup没拦截时)mFirstTouchTarget!=null,当ViewGroup拦截时,则mFirstTouchTarget==null
  • if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) 这层if的意思是:当摁下ACTION_DWON时,就不去考虑之前有没有拦截过该事件,都具有拦截的资格(能够调用onInterceptTouchEvent),当当前事件时UP或者MOVE时,则需要考虑之前有没有拦截过事件,若没拦截就还有机会去拦截该事件,若之前拦截过了,则不再考虑再次调用onInterceptTouchEvent方法,本次后续的时间序列都拦截
  • 当onInterceptTouchEvent不再考虑被调用时,那么走到else里边,intercepted = true,那么当这个intercepted为true时,看他注释:
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.

我们主要看后一句,this view group continues to intercept touches,该ViewGroup继续拦截触摸事件,意思就是后续的事件序列继续拦截。也就是说intercepted=true表示当前事件序列拦截。

  • 假设当前具备调用OnInterceptTouchEvent方法的资格时,还有一层判断:final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;,这层判断是啥意思呢?它产出一个值disallowIntercept,用于判断:
ViewGroup#dispatchTouchEvent

   if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); // restore action in case it was changed
   } else {
            intercepted = false;
   }
  • 这个标记位FLAG_DISALLOW_INTERCEPT是通过方法requestDisallowInterceptTouchEvent(boolean disallowIntercept)来设置的,当requestDisallowInterceptTouchEvent(true)时,if (!disallowIntercept)为false,那么表示不拦截intercepted = false,这也是这个方法名字的由来,”请求不拦截触摸事件”。但是,有时候我们子View调用这个方法会失效,为什么?因为,当面对事件ACTION_DOWN的时候这个标记会被重置,也就是resetTouchState这个方法,那么requestDisallowInterceptTouchEvent(boolean disallowIntercept)这个方法岂不是很鸡肋?这里先TODO一下,等看到滑动冲突的时候再来看如何利用这个方法,源码:

重置FLAG_DISALLOW_INTERCEPT为false

ViewGroup#dispatchTouchEvent

     // Handle an initial down.
    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();
    }
 /**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

FLAG_DISALLOW_INTERCEPT

ViewGroup#requestDisallowInterceptTouchEvent

   @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

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

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

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

ViewGroup—DispatchTouchEvent调用流程分析(分发到View)

  • 遍历childCount,ViewGroup的所有子元素。
  • 判断子元素是否能够接收到事件 a.坐标是否在区域内 b.是否在实行动画
    这个判断是个||的关系,这俩个条件有一个不成立时,则忽略接下来的逻辑直接进入下一次子元素循环(continue)
 if (!canViewReceivePointerEvents(child)
       || !isTransformedTouchPointInView(x, y, child, null)) {
        ev.setTargetAccessibilityFocus(false);
        continue;
  }
  • 若当前事件的坐标在区域内,并且没有在执行动画,那么就去调用dispatch
  • formedTouchEvent(实际上就是调用的子元素的dispatchTouchEvent)注意这里很重要,这里的handled = child.dispatchTouchEvent(event);实际上就已经过渡到View的事件分发流程了,But,我们这里暂时不分析,下一节进行分析
    /**
     * Transforms a motion event into the coordinate space of a particular child view,
     * filters out irrelevant pointer ids, and overrides its action if necessary.
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);//实际上就是调用的子元素的dispatchTouchEvent
            }
            event.setAction(oldAction);
            return handled;
        }

          ......
        return handled;
    }
  • 如果子元素的dispatchTouchEvent方法返回true,那么mFirstTouchTarget就会被赋值同时跳出(break)for循环,赋值过程真实发生在addTouchTarget, 注意这里很重要,mFirstTouchTarget的赋值直接影响事件拦截逻辑,并且ViewGroup向View的事件分发的过渡点也在这个mFirstTouchTarget
   /**
     * Adds a touch target for specified child to the beginning of the list.
     * Assumes the target child is not already present.
     */
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }
  • 如果子元素的dispatchTouchEvent返回false,ViewGroup就会将事件分发给下一个子元素。(如果还有下一个子元素的话)

  • 总结一波:
    * 这个dispatchTransformedTouchEvent()比想象的地位更重要,它作为ViewGroup向View事件过渡的一个点,无论是事件从ViewGroup正常分发到子元素Viewchild.dispatchTouchEvent(),或者是ViewGroup自己处理事件,都要过渡到View,因为ViewGroup这个类并没有onTouchEvent这个方法,并且根据代码显示,它也确实是调用的super.dispatchTouchEvent()*

  • 最后附上ViewGroup的DispatchTouchEvnetn的代码整体概况,已做好注释:


    ViewGroup#dispatchTouchEvent

      if (!canceled && !intercepted) {

           ......

            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;

                // Clean up earlier touch targets for this pointer id in case they
                // have become out of sync.
                 ......

                    final View[] children = mChildren;
                    //遍历childCount,ViewGroup的所有子元素
                    for (int i = childrenCount - 1; i >= 0; i--) { 
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        // If there is a view that has accessibility focus we want it
                        // to get the event first and if not handled we will perform a
                        // normal dispatch. We may do a double iteration but this is
                        // safer given the timeframe.
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }

                        //判断子元素是否能够接收到事件 a.坐标是否在区域内  b.是否在实行动画
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            // Child is already receiving touch within its bounds.
                            // Give it the new pointer in addition to the ones it is handling.
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        resetCancelNextUpFlag(child);

                        //dispatchTransformedTouchEvent实际上就是调用的子元素的dispatchTouchEvent方法
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {

                            // 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();

                            //如果子元素的dispatchTouchEvent方法返回true,那么mFirstTouchTarget就会被赋值同时跳出for循环,赋值过程真实发生在addTouchTarget
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

                        //如果子元素的dispatchTouchEvent返回false,ViewGroup就会将事件分发给下一个子元素。(如果还有下一个子元素的话)
                        // 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();
                }

                ......
            }

        }


            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                //No touch targets so treat this as an ordinary view.
                //mFirstTouchTarget == null,表示上面那一波遍历并没有子View正常的处理了分发下去的事件
                //a.包含俩种情况,ViewGroup没有子View
                //b.子元素了处理了事件,但子View的DispatchTouchEvent返回了false(一般是由于子View的OnTouchEvent返回了false)
                //可以看到这里第三个参数是null,当着参数为null的时候,该方法内回去调handled = super.dispatchTouchEvent(event);
                //由于View是ViewGroup的父类,所以就转跳到了View的事件分发流程里去了
                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.
                       ......
                    target = next;
                }
            }

事件分发流程梳理3-View事件分发和处理流程

到这里,事件已经到达View这个类了,可能是父元素分发给子元素,也可能是父元素自己处理,总之,已经抵达到View这个类里边了。

View—DispatchTouchEvent

  • View#onFilterTouchEventForSecurity
    最外层的一个大判断。当为false时会放弃接下来所有的分发逻辑。那么这个判断是什么意思?
    (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0是判断当前事件的窗体是否被遮盖(OBSCURED是遮盖的意思),这里!=0意思就是,整条判断语句的意思就是,如果当前窗体被遮盖了则返回false,再看下边他那个注释True if the event should be dispatched, false if the event should be dropped.该事件需要被分发,则返回true。该事件需要被丢弃,则返回false。最终我们知道onFilterTouchEventForSecurity这个方法实际上是谷歌提供给我们的一个分发事件的安全过滤策略,判断事件窗体是否有在顶层(窗体遮盖与否),因为真实的场景是我们玩儿手机点点点,肯定都是点的最顶层的View。

    /**
     * Filter the touch event to apply security policies.
     *
     *  依据安全策略,过滤触摸事件。
     *  @param event The motion event to be filtered. 需要被过滤的触摸事件。
     *  @return True if the event should be dispatched, false if the event should be dropped.
     *  该事件需要被分发,则返回true。该事件需要被丢弃,则返回false。
     * @see #getFilterTouchesWhenObscured
     */

    public boolean onFilterTouchEventForSecurity(MotionEvent event) {
        //noinspection RedundantIfStatement
        if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
                && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
            // Window is obscured, drop this touch.
            return false;
        }
        return true;
    }

另外值得一提的是,在ViewGroup的源碼内也进行了这个事件的过滤。

  • OnTouchListener
    判断是否有mOnTouchListener,如果有(非空),则调用它的onTouch。如果onTouch返回true,那么result = true,这个result的值决定onTouchEvent()这个方法是否调用,这就说明了OnTouchListener.onTouch的优先级高于onTouchEvent
           //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;
            }
  • View#onTouchEvent()在DISABLE时的事件处理情况
    注释写的很明白,A disabled view that is clickable still consumes the touch events, it just doesn't respond to them.当View处于DISABLED的时候同样会消耗事件,只是不响应罢了。
        ......

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return clickable;
        }

  • View#onTouchEvent()消费一个事件及OnclickListener的回调
    1.首先看到LONG_CLICKABLECLICKABLE有一个为true都会让clickable为true,那么它就会消费这个事件,即onTouchEvent返回true,无论它是不是DISABLED。
    2.在Up事件内,会触发performClick,如果View设置了onClickListener,那么会触发它的onClick方法.
 ......

    //首先看到```LONG_CLICKABLE```和```CLICKABLE```有一个为true都会让clickable为true,
    //那么它就会消费这个事件,即onTouchEvent返回true。
 final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

 ......

  if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                 case MotionEvent.ACTION_UP:
                     ...
                     performClick()
                     ...
                     break;
               ......
            }

            return true;
        }



    public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

总结

刚才我们梳理完了View的事件分发流程,这里来总结一波,从最初开始,我们从Activity的dispatch调到了ViewGroup的dispatch,然后从ViewGroup的dispatch调到View的dispatch,当View的dispatch返回时这时候实际上是返回到了ViewGroup的dispatchTransformedTouchEvent,那么我们再来回顾一下ViewGroup的dispatchTransformedTouchEvent和dispatchTouchEvent


   ViewGroup的dispatchTouchEvent

  @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
          ......

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
               .....
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
               .....
            }

               .....
        return handled;
    }

那么再追溯到Activity的dispatchTouchEvent

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

这就是事件从Activity一层层分发到View,然后又从View一层层冒泡返回到了Activity这里.下一篇准备分析下从Activity开始之前的事件分发,也就涉及到整个Android底层架构里的IMS系统,ok,本文就到这里,有什么问题请直接指出.

猜你喜欢

转载自blog.csdn.net/user11223344abc/article/details/80354561