一、概要

  对于Android的事件分发机制,刚开始不太了解的人很难搞懂,因为它确实稍微有点复杂,之前我在CSDN上也花了很长时间写过一篇关于Android事件分发机制的文章,现在竟然发现我当时的理解完全是错误的,因此我打算把这片文章重写一下,文章主要分为以下四个部分,1、Android事件分发机制的三个方法,以及它们之间的关系;2、从源码角度理解顶级ViewGroup对事件的分发过程;3、requestDisallowInterceptTouchEvent对事件分发的影响;4、结合实例说明事件分发过程。以下将对这四部分逐一进行讲解。其中很多是参考了《Android开发艺术探索》一书。

二、Android事件分发机制的三个方法

  public boolean dispatchTouchEvent(MotionEvent ev) 用来进行事件分发,如果事件能够传递到当前的View,那么此方法一定能够被调用,它的返回结果受当前View的onTouchEvent下级View的dispatchTouchEvent方法的影响,表示是否要消耗该事件。
  public booleanon onIterceptTouchEvent(MotionEvent ev) 该方法在dispatchTouchEvent方法的内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么在同一个事件序列中,此方法不会再被调用。同一个事件序列的意思是,比如一次滑动事件,它有一个ACTION_DOWN,很多个ACTION_MOVE,以及一个ACTION_UP,那么这些事件就属于一个事件序列。
  public boolean onTouchEvent(MotionEvent ev) 在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()方法返回true就表示要拦截当前事件,接着事件就会交给这个ViewGroup处理,即会调用它的onTouchEvent()方法,如果这个ViewGroup的onInterceptTouchEvent()返回false,那么表示不拦截当前事件,这时事件会继续传递给它的子元素,接着子元素的dispatchTouchEvent()方法就会被调用,如此反复直到事件最终被处理。

三、从源码角度理解顶级View对事件的分发过程

  ViewGroup的事件分发过程,主要在ViewGroup的dispatchTouchEvent()方法中实现的,该方法比较长,我们选取比较主要的一段代码来分析。

// 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;}

  从上面代码我们可以看出,ViewGroup会在如下两种情况下会判断是否要拦截当前事件,事件类型为ACTION_DOWN或者mFirstTouchTarget != null,ACTION_DOWN很好理解,那么mFirstTouchTarger != null呢,从后面的源码我们可以知道,当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值并指向子元素,换句话说,当ViewGroup不拦截事件,并将事件交由子元素处理时,mFirstTouchTarget != null,反过来一旦事件由当前View拦截时,mFirstTouchTarget != null就不成立,那么当ACTION_MOVE和ACTION_UP事件到来时,由于actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null这个条件为false,因此,将会导致ViewGroup的onInterceptTouchEvent方法不再调用,并且同一事件序列中的其它方法都会交给它处理。

四、requestDisallowInterceptTouchEvent对事件分发的影响

  从上面的事件分发过程我们可以了解到,当ViewGroup向子View分发事件时,ViewGroup可以对事件进行拦截并处理,但是有时候,子View并不希望父View拦截事件,这时我们就需要使用到requestDisallowInterceptTouchEvent方法来重置mGroupFlags标记位,该方法一般在子View中被调用,我们设置requestDisallowInterceptTouchEvent(true)之后,ViewGroup将无法拦截除了ACTION_DOWN以外的其它事件,为什么可以做到呢,首先我们看下面的requestDisallowInterceptTouchEvent源码:

/** * {@inheritDoc} */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);    }}

我们结合这段requestDisallowInterceptTouchEvent()方法的源码,以及上面dispatchTouchEvent()方法的部分源码,我们来分析为什么requestDisallowInterceptTouchEvent(true)调用以后,ViewGroup就不会拦截事件了。
requestDisallowInterceptTouchEvent(true)被调用,disallowIntercept 为 true;
1、假设 (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为 true;那么将会return;接着我们到dispatchTouchEvent方法中看到final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;这一行,因为(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为 true,所以此时disallowIntercept = true;接着看下面代码:

if (!disallowIntercept) {        intercepted = onInterceptTouchEvent(ev);        ev.setAction(action); // restore action in case it was changed    } else {        intercepted = false;    }

因为disallowIntercept为true,所以,条件!disallowIntercept不成立,所以会执行intercepted = false,dispatchTouchEvent返回false,即不拦截事件。
2、假设(mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 为 false;即(mGroupFlags & FLAG_DISALLOW_INTERCEPT) = 0, 此时代码会接着会往下执行mGroupFlags |= FLAG_DISALLOW_INTERCEPT;即mGroupFlags = mGroupFlags | FLAG_DISALLOW_INTERCEPT,然后我们再到dispatchTouchEvent方法中看这一句final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
mGroupFlags = mGroupFlags | FLAG_DISALLOW_INTERCEPT
disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0
上面两行代码合起来等价于final boolean disallowIntercept = (mGroupFlags | FLAG_DISALLOW_INTERCEPT & FLAG_DISALLOW_INTERCEPT) != 0,而这段代码又等价于disallowIntercept = (FLAG_DISALLOW_INTERCEPT != 0),即 a | b & b = b,至于为什么,有时间去了解了解java的逻辑运算。因为FLAG_DISALLOW_INTERCEPT != 0,所以disallowIntercept = true,接着会执行intercepted = false;即不拦截事件。

五、结合实例说明事件分发过程

这篇文章已经很长了,实例我将在其他文章中进行讲解

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. [实战示例] 带您深入探讨 Android(安卓)传感器【附源码】
  5. Android支持HTML标签
  6. Android基础入门教程——8.4.4 Android动画合集之属性动画-又见
  7. 让 Android(安卓)支持下拉刷新(Pull Refresh)
  8. Android(安卓)进度暂停和继续
  9. 为数不多的人知道的AndroidStudio快捷键(一)

随机推荐

  1. NAS迁移至OSS,目录迁移是顺序、随机判断
  2. 杂 | PMP项目管理认证
  3. 调用函数以及常用模块
  4. 好用的大数据平台有哪些?
  5. 如何用Python批量修改文件名?
  6. SumSwap独特的staking机制为何如此受欢迎
  7. 前端教程之Intro.js轻松实现新手引导效果
  8. Apache CarbonData 1.0.0发布及其新特性
  9. 下一代大数据处理平台Apache Beam成为Apa
  10. 什么是CPU 上下文切换