Android 事件传递机制研究综述(一)

文章目录
  1. 1. 触摸事件
  2. 2. 控件架构
  3. 3. 事件的分发与拦截机制
  4. 4. 事件的处理机制

Life is like a box of chocolates, you never know what you’re going to get.

因生活中的一些变故和各种琐屑的事,博客许久未更。我们不知道明天,甚至下一分钟,人生将发生什么改变。或许,能做到的只有珍惜现在的每一分钟,尽力做好每一件事。未来不可测,认真做好每件事,快乐过好每一天,足矣。言归正传,以下即是关于 Android 事件传递机制的研究综述。

Android 提供了一套完善的事件分发、拦截以及处理机制,在系统捕捉到用户的输入事件后,保证准确地传递给需要该事件的控件。

触摸事件

触摸事件,即捕捉触摸屏幕后产生的事件。例如,点击一个按钮时,一般会产生如下事件,即按下 Down、移动 Move、取消 Cancel 和离开触摸屏 Up。值得注意的是,一个基本的完整的触摸事件必须由 Down 开始,再到 Up/Cancel 结束,中间的 Move 可有可无。Android 为触摸事件封装了一个 MotionEvent 类,如重写 onTouchEvent() 方法,参数就是一个 MotionEvent。甚至,只要是重写触摸相关的方法,参数一般都含有 MotionEvent。另外,MotionEvent 里还封装了一些东西,如触摸点的横坐标,可以通过 event.getX() 方法和 event.getRawX() 方法取出坐标点。两者区别是,getX() 为获取点击事件距离控件左边界的距离,即视图坐标;getRawX() 为获取点击事件距离整个屏幕左边界的距离,即绝对坐标。再如获得点击的事件类型,像 MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE 等不同的 Action。总结起来,触摸事件很简单,就是一个动作类型加上坐标。

控件架构

Android 中,每个控件都在界面中占有一块矩形的区域,大致分为两类,即 ViewGroup 控件与 View 控件。ViewGroup 控件作为父控件,可以包含并管理多个 View 控件。通过 ViewGroup,整个界面上的控件形成了一个树形结构,即控件树,上层控件负责下层控件的测量与绘制,并传递交互事件。实际上,Activity 中使用的 findViewById() 方法,就是在控件树中以树的深度优先遍历来查找对应元素。另外,在每棵控件树的顶部,都有一个 ViewParent 对象,是整棵树的控制核心,由它来统一调度和分配所有的交互管理事件,进而实现对整个视图的控制。如下图所示:

事件的分发与拦截机制

BlogEventDemo 地址: BlogEventDemo

我们已经知道,View 可以放在 ViewGroup 里面,通过不同的组合实现不同的样式。假设以下情况,View 放在一个 ViewGroup 里面,该 ViewGroup 又放在另一个 ViewGroup 里面,分别设定为 BabyView、SmallViewGroup 和 BigViewGroup。这时,一个触摸事件传递过来,各个控件该如何响应呢?继续往下看。

想象有这样一所学校,一名年级部主任 (BigViewGroup),级别较高;一位班主任 (SmallViewGroup),级别次之;最低级别的,是一个学生 (BabyView)。如今,校长突然下达一项任务,年级部主任 (BigViewGroup) 将这项任务布置给班主任 (SmallViewGroup),班主任 (SmallViewGroup) 又把任务安排给学生 (BabyView)。接着,学生 (BabyView) 完成了任务后,把任务交给班主任 (SmallViewGroup),班主任 (SmallViewGroup) 确定合格后,签字交给年级部主任 (BigViewGroup),年级部主任 (BigViewGroup) 再次审定合格后,就签字交给校长。这样,一个完整的任务流程就结束了。如下图所示:

上述任务流程抽象成以下含代码实例:

设定年级部主任 (BigViewGroup) 为最外层的 ViewGroup;班主任 (SmallViewGroup) 为中间的 ViewGroup;学生 (BabyView) 为最里层的 View。如下图所示:

对于 ViewGroup,重写了以下三个方法,如 BigViewGroup:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("iamasoldier6", "BigViewGroup dispatchTouchEvent" + event.getAction());
return super.dispatchTouchEvent(event);
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.d("iamasoldier6", "BigViewGroup onInterceptTouchEvent" + event.getAction());
return super.onInterceptTouchEvent(event);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("iamasoldier6", "BigViewGroup onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}

对于 View,重写了以下两个方法,如 BabyView:

1
2
3
4
5
6
7
8
9
10
11
@Override 
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d("iamasoldier6", "View dispatchTouchEvent" + event.getAction());
return super.dispatchTouchEvent(event);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("iamasoldier6", "View onTouchEvent" + event.getAction());
return super.onTouchEvent(event);
}

很明显,ViewGroup 级别更高,比 View 多了一个方法 onInterceptTouchEvent(),该方法为事件拦截的核心方法,点击最里层的 BabyView,控制台的 Log 如下:

1
2
3
4
5
6
7
8
16818-16818/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup dispatchTouchEvent0
16818-16818/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup onInterceptTouchEvent0
16818-16818/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup dispatchTouchEvent0
16818-16818/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup onInterceptTouchEvent0
16818-16818/com.iamasoldier6.blogeventdemo D/iamasoldier6: View dispatchTouchEvent0
16818-16818/com.iamasoldier6.blogeventdemo D/iamasoldier6: View onTouchEvent0
16818-16818/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup onTouchEvent0
16818-16818/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup onTouchEvent0

得出结论!

事件的传递顺序:

年级部主任 (BigViewGroup) -> 班主任 (SmallViewGroup) -> 学生 (BabyView)。事件传递的时候,先执行 dispatchTouchEvent() 方法,再执行 onInterceptTouchEvent() 方法。

事件的处理顺序:

学生 (BabyView) -> 班主任 (SmallViewGroup) -> 年级部主任 (BigViewGroup)。事件处理的时候,执行 onTouchEvent() 方法。

事件传递的返回值:true,拦截,流程中断;false,不拦截,流程继续。

事件处理的返回值:true,已处理,不用审核;false,未处理,交给上一级处理。

值得注意的是,默认返回值都为 false

这里,为了方便理解事件拦截,在事件传递中,只着重 onInterceptTouchEvent() 方法,暂时不管 dispatchTouchEvent() 方法,上面整个事件连带方法调用过程如下图所示:

下面稍做改动,假设年级部主任 (BigViewGroup) 觉得自己可以完成,没必要交给班主任 (SmallViewGroup)。因此,事件就被年级部主任 (BigViewGroup) 使用 onInterceptTouchEvent() 方法拦截住,即让 BigViewGroup 的 onInterceptTouchEvent() 方法返回 true,改动后的代码如下:

1
2
3
4
5
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.d("iamasoldier6", "BigViewGroup onInterceptTouchEvent" + event.getAction());
return true;
}

仅改动该方法,点击最里层的 BabyView,控制台的 Log 如下:

1
2
3
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup dispatchTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup onInterceptTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup onTouchEvent0

年级部主任 (BigViewGroup) 把事情干了,没后面的人事情了,上面整个事件连带方法调用过程如下图所示:

同理,若年级部主任 (BigViewGroup) 不打算做,将任务交给班主任 (SmallViewGroup),班主任 (SmallViewGroup) 觉得自己可以完成,没必要交给学生 (BabyView)。因此,事件就被班主任 (SmallViewGroup) 使用 onInterceptTouchEvent() 方法拦截住,即让 SmallViewGroup 的 onInterceptTouchEvent() 方法返回 true,改动后的代码如下:

1
2
3
4
5
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
Log.d("iamasoldier6", "SmallViewGroup onInterceptTouchEvent" + event.getAction());
return true;
}

仅改动该方法,点击最里层的 BabyView,控制台的 Log 如下:

1
2
3
4
5
6
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup dispatchTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup onInterceptTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup dispatchTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup onInterceptTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup onTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup onTouchEvent0

班主任 (SmallViewGroup) 把事情干了,学生 (BabyView) 就不用干了,上面整个事件连带方法调用过程如下图所示:

事件的处理机制

上面讲的是事件的分发与拦截机制,下面再看事件的处理机制。对于学生 (BabyView),一般来说,处理完任务后会向班主任 (SmallViewGroup) 报告,需要班主任 (SmallViewGroup) 的审核,所以,学生 (BabyView) 的的事件处理默认返回 false。假设学生 (BabyView) 完成不了任务,也就没法报告班主任 (SmallViewGroup),直接返回 true。改动后的代码如下:

1
2
3
4
5
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("iamasoldier6", "View onTouchEvent" + event.getAction());
return true;
}

仅改动该方法,点击最里层的 BabyView,控制台的 Log 如下:

1
2
3
4
5
6
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup dispatchTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup onInterceptTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup dispatchTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup onInterceptTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: View dispatchTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: View onTouchEvent0

显然,事件传递与之前一样,但是事件处理到学生 (BabyView) 就结束了,上面整个事件连带方法调用过程如下图所示:

同理,若学生 (BabyView) 完成任务,交由班主任 (SmallViewGroup) 审核,不过,审核没通过,没法交给年级部主任 (BigViewGroup),整个事件到此为止,即班主任 (SmallViewGroup) 的 onTouchEvent() 方法返回 true,改动后的代码如下:

1
2
3
4
5
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("iamasoldier6", "SmallViewGroup onTouchEvent" + event.getAction());
return true;
}

仅改动该方法,点击最里层的 BabyView,控制台的 Log 如下:

1
2
3
4
5
6
7
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup dispatchTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: BigViewGroup onInterceptTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup dispatchTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup onInterceptTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: View dispatchTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: View onTouchEvent0
19761-19761/com.iamasoldier6.blogeventdemo D/iamasoldier6: SmallViewGroup onTouchEvent0

事件处理到班主任 (SmallViewGroup) 就结束了,上面整个事件连带方法调用过程如下图所示:

至此,Android 事件传递机制研究综述(一)到此结束,(二)将是从相关源码角度分析事件的分发、拦截以及处理机制,未完待续。

本人才疏学浅,如有疏漏错误之处,望读者中有识之士不吝赐教,谢谢。

1
Email: [email protected] / WeChat: Wolverine623

您也可以关注我个人的微信公众号码农六哥第一时间获得博客的更新通知,或后台留言与我交流