android 事件机制图文详解-从源码角度分析彻底理解事件传递机制(下)

在上篇中,我们分别对dispatchTouchEvent(),onInterceptTouchEvent()以及onTouchEvent()这三个方法进行了比较详细的分析,但是其实在执行onTouchEvent方法前,还有一个比较重要的触摸函数,那就是View.OnTouchListener: boolean onTouch(View v, MotionEvent event)只不过这个函数需要我们在相应的控件中注册OnTouchListener监听接口,这个稍后会分析。

先来看看本篇分分段,共3个部分:

1. activity是如何把事件分发给viewgroup

2.viewgroup的事件分发与拦截过程

3.view的事件分发处理过程

那就从第一个开始呗。

1. Activity是如何把事件分发给ViewGroup?


          还记得这张事件传递处理的流程图吗?细心回忆一下后,从图中可以看到入口是Activity,那么我们就从Activity开始吧。

         当一个点击事件发生时,事件最先传递给当前Activity,然后由Activity的dispatchTouchEvent()进行对事件分发,因此我们先从Activity的dispatchTouchEvent()开始分析

我们先看一下Activity的dispatchTouchEvent()的源码:

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

从源码可以看出Activity的dispatchTouchEvent()方法也是把事件是交给了Activity所依附的窗体,即调用Window.superDispatchTouchEvent(ev);如果superDispatchTouchEvent()返回了true,那么整个事件传递处理将会结束,返回false就意味着没有处理事件,最终交给Activity的onTouchEvent(ev)处理。这里跟我上一篇分析的流程是相对应的。这里调用前还有个onUserInteraction(),这个方法其实是做一些用户交互操作的初始化内容,该方法是个空方法,需要时我们可以重写该方法。

那么window又是如何把事件传递给viewgroup的呢?

public abstract boolean superDispatchTouchEvent(MotionEvent event);
public abstract class Window {}
通过源码我们知道 superDispatchTouchEvent是个抽象方法,也就是说window是个抽象类,所以我们必须找到其实现类才行。我们直接看看getWindow()方法实现:

public Window getWindow() {
        return mWindow;
    }

返回了mWindow,那么我直接找mWindow在哪创建的即可,最后找到了:

android 事件机制图文详解-从源码角度分析彻底理解事件传递机制(下)_第1张图片

原来 Window的实现类是PhoneWindow,既然这样我们直接进入PhoneWindow源码,找到PhoneWindow#superDispatchTouchEvent()方法的实现。源码如下:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
     return mDecor.superDispatchTouchEvent(event);
}
这里PhoneWindow直接把事件传递给了 DecorView, 调用的是 mDecor #superDispatchTouchEvent(event),那mDecor又是什么?其实我们前面好几次都提到这个类,其实DecorView是PhoneWindow的一个内部类:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

可以看到DecorView继承自FrameLayout,所以DecorView也是一个ViewGroup,因此到了这里我们就明白了,事件发生后通过Activity#dispatchTouchEvent(event)方法分发给了当前所依附的Window窗口,而Window的实现类是PhoneWindow,通过调用PhoneWindow#superDispatchTouchEvent(event)方法,把事件直接传递了给了其内部类DecorView,而内部类DecorView又是个ViewGroup,此时事件已通过DecorView#superDispatchTouchEvent(event)方法传递给了ViewGroup.

那么有人会问了,DecorView到底是什么呀?下面我随意新建一个项目运行(去掉了标题栏哈)。然后通过android提供的工具hierarchyviewer查看布局关系图:

android 事件机制图文详解-从源码角度分析彻底理解事件传递机制(下)_第2张图片

这样就大概能明白DercoView是什么了吧。DercoView这里就不再过多分析,回归本篇的主题。

通过上面的分析,我们都知道了点击事件是如何通过Activity传递给ViewGroup了。


2.ViewGroup的事件分发和拦截

下面我们接着对ViewGroup是如何分发,拦截和处理进行分析。

我们回忆一下,上一篇文章中事件传递和处理流程:

当TouchEvent发生时,首先Activity将TouchEvent通过getWindow().superDispatchTouchEvent(event)把事件分发到当前活动窗口(PhoneWindow),之后是顶级窗口的DecorView,调用了DecorView的dispatchTouchEvent,DecorView继承自ViewGroup,所以这里实际上就进入了ViewGroup层面的dispatchTouchEvent,然后由dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则交给当前这个view的onTouchEvent处理,如果dispatchTouchEvent返回 false 则交给当前这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给当前这个view的onTouchEvent来处理,如果 interceptTouchEvent 返回 false ,那么就继续传递给子view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,而且都是 onTouchEvent 来接收。而如果传递到最上面的 onTouchEvent 也返回 false 的话(包括Activity),这个事件就会“消失”, 而且接收不到下一次事件。(这指的是 down 到 up 之间的一系列事件)。Touchevent 中,返回值是 true ,则说明消耗掉了这个事件。

回忆完了,我们先有点印象就行,因为通过下面源码分析后,其实内部还有很多细节,最后我会做一个比较完整的总结,这里我们先从ViewGroup的事件分发开始分析:

查看ViewGroup#dispatchTouchEvent()方法的源码,该方法源码比较多,我们只看主要的(省略部分没必要的源码),然后一段段来分析:

 @Override
public boolean dispatchTouchEvent(MotionEvent ev) {   

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                //重置mGroupFlags的值-mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT
                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); // 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在两种情况下会判断是否拦截当前事件:当事件类型为MotionEvent.ACTION_DOWN 或者mFirstTouchTarget != null时。ACTION_DOWN是按下时当触摸事件,但mFirstTouchTarget又是什么鬼!其实当ViewGroup不拦截事件并将事件分发给子类时mFirstTouchTarget会被赋值并且指向子View,当然如果ViewGroup拦截了事件,这也就意味着mFirstTouchTarget肯定为null,那么当ACTION_UP和ACTION_MOVE响应时,条件为if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)肯定不会成立,那么ViewGroup#onInterceptTouchEvent()方法也不会再被调用,也就是MotionEvent.ACTION_DOWN操作被一旦被拦截,同一序列的事件(ACTION_UP和ACTION_MOVE)都会默认交给当前ViewGroup#onTouchEvent()处理。如果MotionEvent.ACTION_DOWN事件没有被拦截那么mFirstTouchTarget!=null是肯定成立的,这时ACTION_UP和ACTION_MOVE是否被拦截就要看disallowIntercept的取值或者ViewGroup#onInterceptTouchEvent()方法本身的返回值了。

顺便说明一下从源码可以看出ViewGroup#onInterceptTouchEvent()默认返回false

public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
    }

关于disallowIntercept,这是一种特殊情况可以控制ViewGroup的永远不拦截子view的事件(ACTION_DOWN除外),从源码我们可以看到

final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

因此只有disallowIntercept为false时,才会调用ViewGroup#onInterceptTouchEvent()方法,反之,disallowIntercept为true时,就永远不会调用ViewGroup#onInterceptTouchEvent()方法也就是ViewGroup无法拦截子view的(ACTION_DOWN除外)事件,但 disallowIntercept的取值,取决于mGroupFlags ,而mGroupFlags的值又是那里来的?不知道大家有没用过下面这个函数,这个函数注释写得很明白,如果当你不想子view的事件被父view拦截时,可以在子view中调用父view的requestDisallowInterceptTouchEvent(true)方法,并设置为true(也就是mGroupFlags |= FLAG_DISALLOW_INTERCEPT),这时父view就不会拦截子view的事件了。所以这个函数就是用来设置 mGroupFlags 的值,当传入值为true时,表示父view不拦截子view(ACTION_DOWN除外)的事件。

/**
     * Called when a child does not want this parent and its ancestors to
     * intercept touch events with
     * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.
     *
     * <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>
     * 
     * @param disallowIntercept True if the child does not want the parent to
     *            intercept touch events.
     */
   public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

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

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

可能大家还有个疑问,为什么是除ACTION_DOWN事件以外的其他事件,其实是因为ViewGroup在进行事件分发时,如果当前事件时ACTION_DOWN,那么 mGroupFlags 的值会被重置,将导致子view调用ViewGroup#requestDisallowInterceptTouchEvent(true)设置mGroupFlags |= FLAG_DISALLOW_INTERCEPT的标记位失效。因此事件为ACTION_DOWN时,ViewGroup肯定会调用自己的onInterceptTouchEvent()方法判断是否要拦截此事件。这个从源码也可看出来。但mGroupFlags的值是在那里被重置的呢?看回之前的源码,我在ViewGroup#dispatchTouchEvent()中有注明,那就是resetTouchState()方法,我们再看一下这个方法的实现就了然了。

/**
     * Resets all touch state in preparation for a new cycle.
     */
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//重置mGroupFlags的值
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }

这也就间接说明了调用ViewGroup#requestDisallowInterceptTouchEvent(true)方法并不能影响ViewGroup对ACTION_DOWN事件的处理。

下面是我第一次使用ViewGroup#requestDisallowInterceptTouchEvent(true)方法的情景,提供给大家参考参考。

第一接触这个方法是因为ViewPager,当时用ViewPager来实现左右滑动切换tab,然后在tab的某一项中嵌入了水平可滑动的View就会滑动变得混淆起来,比如想滑动tab项中的可水平滑动的控件,却导致tab切换。当时我就是利用这个方法解决了滑动混淆的问题。

解决方案:嵌套的子view控件中注入ViewPager实例(调用控件的getParent()方法),然后在onTouchEvent里面告诉父View,也就是ViewPager不要拦截该控件上的触摸事件,处理代码如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()){
      case MotionEvent.ACTION_MOVE: 
          getParent().requestDisallowInterceptTouchEvent(true);//不拦截
          break;
      case MotionEvent.ACTION_UP:
      case MotionEvent.ACTION_CANCEL:
          getParent().requestDisallowInterceptTouchEvent(false);//拦截,恢复正常
          break;
      }
     return super.onTouchEvent(event);
}

到这里,我们先总结一下viewgroup的事件分发,从ViewGroup#dispatchTouchEvent()中可以知道,事件传递到dispatchTouchEvent()时,先通过(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)条件判断是否进行拦截操作,如果满足条件,再根据disallowIntercept的值判断是否可以调用ViewGroup#onInterceptTouchEvent()方法。而disallowIntercept的值取决于mGroupFlags,这个值决定了在ViewGroup不拦截ACTION_DOWN事件的条件下,ViewGroup是否可以拦截子view的事件,如果我们在子view中调用ViewGroup#requestDisallowInterceptTouchEvent(true),并设为true(mGroupFlags|=FLAG_DISALLOW_INTERCEPT)时,ViewGroup将无法拦截除了ACTION_DOWN事件以外的其他事件(ACTION_UP和ACTION_MOVE等),如果设置为false,则ViewGroup可以拦截子View的事件,但是是否拦截还需根据ViewGroup#onInterceptTouchEvent()方法的返回值。当然由于执行ACTION_DOWN事件时,调用resetTouchState()方法重置mGroupFlags的值,所有mGroupFlags的值对ACTION_DOWN事件没影响。如果ViewGroup拦截了ACTION_DOWN事件,那么后续的所有序列事件都默认交给当前ViewGroup处理并不会再调用ViewGroup#onInterceptTouchEvent()。

这里还可以得出一些结论:

1.ViewGroup#onInterceptTouchEvent()方法并不是每次事件都会被调用,如果我们提前处理所有点击事件,应该选中ViewGroup的dispatchTouchEvent()方法。

2.我们可以在子view中调用ViewGroup#requestDisallowInterceptTouchEvent(true)方法来控制父view永远不拦截子view除了ACTION_DOWN事件以外的其他事件(ACTION_UP和ACTION_MOVE等)。当然这里的前提条件是事件可以传递到该viewgroup同时viewgroup没有拦截ACTION_DOWN事件。


我们接着往下,看看当ViewGroup不拦截时,事件是如何分发给其子view的
//既然是不拦截那么intercepted肯定为false,这时事件一般为不会结束,所以canceled也为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;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);
                    //获取子view的数量,准备遍历所有子view
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        //查找可以接收事件的子view
                        final ArrayList<View> preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //开始遍历子view并判断能否接收点击事件
                        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;
                            }
                            //判断子view是否在播放动画和点击事件的坐标是否落在子view的区域内
                            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);
               //这里是重点哦,把事件分发给子view就是在这个
               dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法里面执行的
                           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();
                           //如果子view处理点击事件即dispatchTouchEvent返回true,newTouchTarget会被赋值
                           //这时还记得我们前面提到的mFirstTounchTarget变量吗?此时mFirstTounchTarget
                           //会在addTouchTarget
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                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();
                    }
通过源码我们可以知道只要 ViewGroup不拦截事件,同时这时事件也不是结束事件的话,那么  if  (! canceled  && ! intercepted ) 必将成立。进入该if的方法体中首先会去遍历ViewGroup的所有子View,然后判断子View能否接收点击事件————通过两个条件判断, 先判断子view是否在播放动画和点击事件的坐标是否落在子view的区域内。如果有子view满足这两个条件,那么事件将交给该子view处理。那么事件到底时怎么样分发给子view的呢?这里其实是在dispatchTransformedTouchEvent(evfalsechildidBitsToAssign)方法里调用了子view的 child .dispatchTouchEvent( event )方法,就这样子view就接收到点击事件了,这也就完成了一轮事件分发。我们直接看看ViewGroup#dispatchTransformedTouchEvent方法源码:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
                           //省略其他代码
             //判断子view是否为null,从而决定调用父view的super.dispatchTouchEvent(event)
            //还是子view的child.dispatchTouchEvent(event)
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
        return handled;
    }

接着如果 ViewGroup#dispatchTransformedTouchEvent返回true也就意味着 子view的dispatchTouchEvent返回值为true,那么说明子view消费了事件 这时还记得我们前面提到的mFirstTounchTarget变量吗?此时mFirstTounchTarget在addTouchTarget (child, idBitsToAssign)被赋值。当然如果ViewGroup#dispatchTransformedTouchEvent返回false也就意味着子view的dispatchTouchEvent返回值为fasle,ViewGroup就会把事件分发给下一个子view(前提是还有下一个子view)。下面顺便看看addTouchTarget(child, idBitsToAssign)源码:

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

由源码可知mFirstTouchTarget确实是完成了赋值,但这有什么作用啊?

还记得这条判断语句吗?if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) 这也就说明

mFirstTouchTarget将影响ViewGroup除了MotionEvent.ACTION_DOWN以外的其他事件的拦截策略,如果mFirstTouchTarget为null,那么ViewGroup就默认拦截接下来的依序列(ACTION_UP和ACTION_MOVE等)的事件。


这里我们小结一下:

如果ViewGroup不拦截子view的点击事件,那么ViewGroup将会去循环遍历所有的子View, 然后根据子view是否在播放动画和点击事件的坐标是否落在子view的区域内这两个条件判断是否可接收点击事件,如果满足这两个条件,将会通过ViewGroup#dispatchTransformedTouchEvent方法去调用child.dispatchTouchEvent(event),从而将点击事件交给该子view处理,如果子view的dispatchTouchEvent(event)方法返回true(这一般都是因为子view的onTouchEvent()返回了true消费了事件),那么将会执行ViewGroup#addTouchTarget(child, idBitsToAssign)方法完成对mFirstTouchTarget变量的赋值,从而影响ViewGroup的事件拦截策略。当然如果ViewGroup最后都没有遍历到适合到子view处理点击事件,这里分两个情况,一个是没有子view,另一个则是子view对事件处理返回了false即未消费事件,那么事件将交由ViewGroup自己处理,实际上是调用了super.dispatchTouchEvent(event)。


那么,接下来我们来看看,ViewGroup在没有找到适合子类时是如何自己处理事件的(注意哦,这里仍然是ViewGroup未拦截子view事件的情况哈):

在ViewGroup#dispatchTouchEvent()方法体中继续往下查看源码,我们可以发现:

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

确实mFirstTouchTarget为null,仍然会去调用ViewGroup#dispatchTransformedTouchEvent(evcancelednull, TouchTarget.ALL_POINTER_IDS)方法,但请注意,此时第3个参数child为null!!!很显然这里肯定会去调用super.dispatchTouchEvent(event)也就跳转到GroupView的父view了,那么GroupView父view是谁啊?那还用说么?当然是顶级View啊!!!也就是点击事件交由View来处理。


3. View对点击事件的处理过程

看到了这里时我搜索了一下ViewGroup的源码,结果没发现ViewGroup的onTounchEvent(),也就是说ViewGroup并没有重写父view的onTounchEvent(),因此也可以判断出,ViewGroup通过间接调用super.dispatchTouchEvent(event)方法来调用父view(也可以说是ViewGroup自己)的onTounchEvent().

那我们先看看view#dispatchTouchEvent(MotionEvent event)方法源码:

 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;
            }
            //在这里执行了onTouchEvent(event)
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        return result;
    }

代码不算多,但我们不全部看,只看贴出来的源码。

View对点击事件的处理过程不太复杂,因为view只是一个单独的元素,没有子元素因此它只能自己处理事件。

li != null && li.mOnTouchListener != null这个条件是判断监听接口是否有注册

(mViewFlags & ENABLED_MASK)== ENABLED  而这个条件是判断控件是否可用,这个条件一般都为true

li.mOnTouchListener.onTouch(this, event),这个是判断实现这个监听接口的onTouch方法是返回true还是false;

因此主要决定OnTouchEvent()是否被调用的因素还是在onTouch()函数身上,因此有了以下结论:

1.onTouch()方法总是在onTouchEvent()之前执行(还记得文章开头提到过的onTouch吧

2.只有当onTouch()方法返回false时,才会去调用onTouchEvent()方法

3.当onTouch()方法返回true时,onTouchEvent()方法不会执行

嗯,那么我们在看看onTouchEvent()源码,先贴部分源码,

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


由代码可知上面是View处于不可用状态点击事件的处理过程,由注释我们可知道即使View处于不可用状态照样会消费点击事件。

下面再看看View处于可用状态下对点击事件对处理

if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        // take focus if we don't have it already and we should in
                        // touch mode.
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                        if (prepressed) {
                            // The button is being released before we actually
                            // showed it as pressed.  Make it show the pressed
                            // state now (before scheduling the click) to ensure
                            // the user sees it.
                            setPressed(true, x, y);
                       }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();//onClick()点击事件的处理
                                }
                            }
                        }

                       .................
                    }
                    break;
            }

            return true;
        }

源码相当长,这里省略一些没必要的代码。

从源码可知道,只要view的CLICKABLELONG_CLICKABLE有一个为true就会消费该事件,此时onTouchEvent方法返回true

同时在 MotionEvent.ACTION_UP事件中会触发performClick()(其实方法内部就是对onClick()点击事件的处理),这也说明onTouchEvent事件如果不执行的话,onclick()事件也别想得到执行,直接上performClick()的源码:


public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);//对onClick()点击事件的处理
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

可能到这里大家会有疑问,怎么知道view的CLICKABLELONG_CLICKABLE是否为true啊?其实这就跟view控件本身有关了。例如TextView本身就不具备可点击状态这时CLICKABLE和LONG_CLICKABLE肯定为false,Button本身就是可点击状态所以CLICKABLE和LONG_CLICKABLE肯定为true。不对啊,有时我们也可以点击TextView啊?确实,但别忘记了,那时是已经为TextView设置了setOnClickListener()事件,所以TextView内部的CLICKABLE会变为true。因此这点也意味着我们可以通过设置setOnClickListener()和setOnLongClickListener()改变view控件CLICKABLE和LONG_CLICKABLE的值,从而是不可点击状态的view变为可点击状态。

看一下两个方法的源码就更清晰了:

public void setOnClickListener(@Nullable OnClickListener l) {
        if (!isClickable()) {
            setClickable(true);
        }
        getListenerInfo().mOnClickListener = l;
    }

public void setOnLongClickListener(@Nullable OnLongClickListener l) {
        if (!isLongClickable()) {
            setLongClickable(true);
        }
        getListenerInfo().mOnLongClickListener = l;
    }

最后,把两篇文章结合一下,然后来个比较完整的总结:

当TouchEvent发生时,首先Activity将TouchEvent 通过getWindow().superDispatchTouchEvent(event)把事件分发到当前活动窗口(PhoneWindow) 之后是窗口的DecorView,调用了DecorView的dispatchTouchEvent,DecorView继承自ViewGroup,所以这里实际上就进入了ViewGroup层面的dispatchTouchEvent ,然后由当前ViewGroup#dispatchTouchEvent 方法进行分发,这时当前ViewGroup#dispatchTouchEvent 方法内部会去调用 ViewGroup#onInterceptTouchEvent方法,然后判断是否要拦截当前事件,如果onInterceptTouchEvent()返回true,则表示拦截,这时ViewGroup#dispatchTouchEvent内部会去调用父View.dispatchTouchEvent()(这里是通过ViewGroup# dispatchTransformedTouchEvent()方法间接调用的 ),如果控件注册了 OnTouchListener监听,则交给onTouch方法进行处理, 如果onTouch返回true,则事件被消费,直接返回并结束。 如果onTouch方法返回false, 则交给当前这个顶级View的onTouchEvent来处理(其实这里可以理解为调用的是ViewGroup自己的onTouchEvent(),因为ViewGroup本身并没有去重写父View的onTouchEvent(),至少我在ViewGroup的源码没找到),如果 interceptTouchEvent 返回 false,则表示不拦截,ViewGroup#dispatchTouchEvent 方法内部就继续执行并去遍历子view(前提是ViewGroup有子view),试图找到合适的子view并调用子view的dispatchTouchEvent(),把事件传递给子view。如果GroupView没有子 View或者子view都没有处理事件即子view的dispatchTouchEvent()返回了false,那么ViewGroup#dispatchTouchEvent 方法内部就会去调用super.dispatchTouchEvent方法,这里实际上是去调用父View.dispatchTouchEvent() ,如果控件注册了 OnTouchListener监听,则交给onTouch方法进行处理, 如果onTouch返回true,则事件被消费,直接返回并结束。 如果onTouch方法返回false, 则交给当前这个顶级View(也就是ViewGroup自己)的onTouchEvent来处理,onTouchEvent()返回true,则消费了事件,如果返回了false,则这个事件最终会交给Activity#onTouchEvent来处理。


到此,android 事件机制终于分析完了啊!心好累啊。。。




你可能感兴趣的:(android,事件机制图文详解从源码角)