Android View 点击事件传递完结篇

  • 知识点1:
    事件序列:点击屏幕产生的一系列事件,这个事件序列中可能包含 down 、move 、up 等集合。
    Android 会分别对这个事件序列中的某一个事件进行处理,每次分发一个事件,比如先分发 down, 等 down 被处理完,然后再分发 move, 总之就是得一个一个进行处理,不能同时处理。

  • 知识点2:
    View 的一系列事件都是先从上而下递归,如果这个事件没有被消耗掉,那么最后又会由下往上传递,最后交给 Activity 处理,我这里暂时称为空消耗。

  • 知识点3:
    Android 默认会把 View 点击事件的焦点设置给 View 视图中第一个能够获取焦点的 “View”, 记住这里是一个 View ,而不是 ViewGroup。

根据上面的结论,我们在处理 ListView 的 itemView 和 itemView 里面的 checkBox 点击事件冲突时,此时可以对 checkBox 做以下处理:

1
android:focusable="false"

原因就是 checkBox 是一个View,itemView 是一个 ViewGroup,checkBox 得到焦点的优先级高于 itemView, 所以对 itemView 设置事件监听没有效果,处理就是我们需要让 checkBox 得不到焦点。

方法介绍:

在事件传递的过程, 经常遇到以下几个方法:

  • boolean dispatchTouchEvent(MotionEvent ev) {}

事件分发方法,View 和 ViewGroup 都有此方法 (ViewGroup 继承自 View)

结果返回 true 时:说明当前 View 或 ViewGroup 消耗了这个事件(有可能是一个空消耗),不需要再往后分发该事件,同时准备接收下一个事件。

结果返回 false 时:将该事件继续往下或往上分发传递(往上或往下取决于当前的 View 或 ViewGroup 是否是 VIew 树中最后一个视图,如果是最后一个视图则往上返回分发传递)


  • boolean onInterceptTouchEvent(MotionEvent event){}

拦截事件方法,ViewGroup 中特有的方法,View 中没有。

结果返回 true 时:说明 ViewGroup 拦截该事件

结果返回 false 时:说明 ViewGroup 不拦截该事件(Android 所有 ViewGroup 默认不拦截任何事件)


  • boolean onTouch(View v, MotionEvent event) {}

这个是一个特殊的方法,我暂时称它为触摸事件的开关。因为它的返回值影响到 dispatchTouchEvent 方法里面代码的执行顺序以及决定 dispatchTouchEvent 的返回值 ,onTouch 比 onTouchEvent 先执行。当然,你也可以在它返回结束之前干点别的事情,

结果返回 true 时:dispatchTouchEvent 直接返回 true(前提是一定要实现了 OnTouchListener 这个接口),后面的 onTouchEvent 一定不会被调用。

结果返回 false 时:onTouchEvent 一定被调用。


  • boolean onTouchEvent(MotionEvent event){}

消耗事件方法,点击事件 onClick 就是在这个方法里面, onClick 只在 up 时会调用,View 和 ViewGroup 都有此方法; onTouchEvent 也决定 dispatchTouchEvent 的返回值,正常情况下 dispatchTouchEvent 的返回值与 onTouchEvent 返回值相同。

结果返回 true 时:说明这个事件被消耗处理掉了,然后代码执行会回到 dispatchTouchEvent 分发方法,让 dispatchTouchEvent 分发方法最终也返回 true, 这样其它的 View 和 ViewGroup 的 onTouchEvent 都不会再被调用。(View 该方法默认返回 true)

结果返回 false 时:说明不消耗该事件,然后回到 dispatchTouchEvent 方法,让 dispatchTouchEvent 方法返回 false,同时该 View 是 ViewTree 中最后一个 View ,则会把该事件返回给父 View, 最终返回给 Activity 处理,如果不是最后一个 View , 则继续向下分发该事件,所以其它的 View 或 ViewGroup 的 onTouchEvent 可能会被调用。

当你自定义 View 的时候,你想让你的 View 响应 onClick ,只需重写 onTouchEvent 方法,让其直接 return super.onTouchEvent(event); 或者让其 return true ,再或者在 return ture 之前调用一遍 super.onTouchEvent(event)。

当 dispatchTouchEvent 在进行事件分发的时候,只有前一个 action 返回 true,才会触发下一个 action(也就是说 dispatchTouchEvent 返回 true 才会进行下一次 action 派发), 拿点击事件举例子,点击一个按钮,事件序列通常是先 down ,然后是 up , 因为点击事件的 onClick 方法是在 up 时触发的,所以在处理 down 的时候,onTouchEvent 必须返回 true, 这样才能执行继续处理后面的 up, 不然 up 事件永远不会被分发下来。

dispatchTouchEvent 分析

  • ViewGroup -> dispatchTouchEvent()

源代码太长,这里就不粘贴了
当一个新的事件序列触发,dispatchTouchEvent 先处理 down, 在处理 down 的时候会把上一个事件序列清空;然后检查当前自身 ViewGroup 是否要拦截, dispatchTouchEvent 分发方法有一个 final boolean 的变量 intercepted,它决定了 ViewGroup 是否需要执行拦截,intercepted 的值通常在 down 的时候被确定,该值和 onInterceptTouchEvent 返回的值一样。也就是说 ViewGroup 第一次不拦截,后面的事件都不用再走 onIntercepTouchEvent 判断,一路畅通。

ViewGroup 提供了一个 requestDisallowInterceptTouchEvent(boolean disallowIntercept) 方法来禁用执行 onInterceptTouchEvent 拦截方法(requestDisallowInterceptTouchEvent 本身是接口 ViewParent 的一个抽象方法,ViewGroup 实现了该抽象方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@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);
}
}

dispatchTouchEvent 中还有的就是对子 View 的遍历,childView 等于空时调用 super.dispatchTouchEvent(event) ,否则调用 childView.dispatchTouchEvent(event)。

结语

关于 View 事件传递的分析到这里就结束,有很多具体的地方并没有讲, 将代码转成汉字需要大量的时间,故有的地方去领会即可。