前言
随着科学技术的发展,智能手机早已成为我们当代人身边必不可少的“伙伴”之一,堪比对象女友。每天我们对着手机重复的做着点击
、滑动
操作,而手机则随着我们的操作给我们展示她的精彩。
…
废话到此结束。
看到这里,即使不是作为移动端码农的你也应该知道触摸事件对手机(经典键盘机除外)的重要性了。
什么是触摸事件
顾名思义,就是触摸手机屏幕后产生的事件。这时候请你拿出手机,点击屏幕中的某个按钮(不要松手),移动一段距离,松手。
这个过程一般会产生如下几个事件:
- 点击(Down)事件
- 移动(Move)事件
- 松手(Up)事件
Android为我们封装好了一个触摸事件类MotionEvent
,上述的三个过程分别对应着MotionEvent
中的MotionEvent.ACTION_DOWN
、MotionEvent.ACTION_MOVE
、MotionEvent.ACTION_UP
事件类型,我们可以以此来实现不同的逻辑,即事件的分发处理。所谓触摸事件的分发,实际上可以理解为MotionEvent
事件的分发过程,即当一个MotionEvent
产生了之后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是分发过程。
事件三剑客
一般事件的分发过程是由事件三剑客(方法)来共同完成的。
/**
* 剑客一:用于事件的分发
*/
public boolean dispathTouchEvent(MotionEvent ev)
/**
* 剑客二:在剑客一种被调用,用于事件的拦截
*/
public boolean onInterceptTouchEvent(MotionEvent ev)
/**
* 剑客三:在剑客一种被调用,处理点击事件,true:消耗了当前事件 false:当前view无
* 法再次接收事件
*/
public boolean onTouchEvent()(MotionEvent ev)
三剑客的关系如下图所示(以Activity的dispathTouchEvent为例)
分析可知:
1. 触摸事件ev类收到点击的ACTION,会回调onUserInteraction方法,一般项目中我们把一些需要用户开始触摸时就需要执行的任务代码放在这里。
2. 接下来触摸事件ev会传递给Activity窗口绑定的根视图rootView
(View/ViewGroup),如果根视图也有子视图,事件ev会一级一级的分发下去,如果在这个过程中ev被消耗了,事件就此结束分发,否则进入步奏3。
3. 所有的视图布局都没有消耗掉ev事件,就会调用Activity的onTouchEvent()方法。下面会具体讲诉。
Android界面简析
在具体讲诉前,我们先来了解下的android的界面架构。如果说手机是一个学校,那么手机中的每一个APP(应用)都是学校里的一道道独特风景,正是它们,构成了学校的魅力。而每个APP都是由一个个Activity组成的。
还是在说废话…
如下图所示,我们清晰的看到每个Activity
都会包含一个Window
对象。而window
对象通常由PhoneWindow
来实现。PhoneWindow
将一个DecorView
设置为整个应用窗口的根View。它将屏幕分成两部分,一个是TitleView,另一个是ContentView
(也就是大家熟悉的ContentView布局)。ContentView是一个ID为content
的FrameLayout
,而我们一直写的activity_xx.xml布局就是设置在这样一个FrameLayout里。
DecorView
将要显示的具体内容呈现在了PhoneWindow
上,这里面的所有View的监听事件(点击、滑动等操作)都通过一个名为WindowManagerService
来进行接收(具体可看深入理解android卷三),并通过Activity来回调相应的监听。
为了让大家更好的理解,我们来写一个小demo如下
运行结果如图
小场景,见真理
场景一
我们写一个最简单的demo如下
运行程序,点击button,看到log输出如下:
修改dispathTouchEvent,直接return false
;
运行程序,点击button,是不是看到控制台什么都没有输出。可见事件传递到activity的根视图就被结束分发了。下面已场景二来具体探究下这个过程。
场景二
假如在大学中,学校交给了数学老师一个任务,老师讲这项任务布置给了女班长,而女班长又将这个任务交给了帅气的我,我千辛万苦的将这个任务完成了,然后交给了女班长,女班长觉得完成的不错,夸了帅气的我几句(暗恋上了),然后将任务提交给了老师,老师看了下也觉得完成的不错,就把任务提交给学校了。
依据上面的场景,我们设计一个场景实例如下
- 老师——TeacherViewGroup
- 女班长——MonitressViewGroup
- 帅气的我——HandsomeMyView
布局层次如下图所示
TeacherViewGroup和MonitressViewGroup代码如下,重写了三剑客方法
HandsomeMyView代码如下,view是没有剑客2(方法)onInterceptTouchEvent()
的
点击帅气的我
可以看见log打印如下
可以看见一般事件都有两个过程
- 传递过程 : 老师(TeacherViewGroup)——>女班长(MonitressViewGroup)——>帅气的我(HandsomeMyView)
- 处理过程 : 帅气的我(HandsomeMyView)——>女班长(MonitressViewGroup)——>老师(TeacherViewGroup)
传递的过程方法:剑客1(dispatchTouchEvent)、剑客2(onInterceptTouchEvent)
处理的过程方法:剑客3(onTouchEvent)
为了让大家更好的理解,整理视图如下:
从中我们看出触摸事件ev会按照子View加入ViewGroup先后顺序相反
的顺序,依次有机会去消费此触摸事件ev,即最后加入的最先有机会消费此触摸事件(消费的前提是,触摸点在这个子View的视图范围之内)。简而言之,传递由外向内,消费(处理)由内向外。
在前面的事件三剑客中细心的同学会发现,他们的返回值都是boolean
类型,那么true和false分别代表什么意义呢?
在这里我先告诉大家结论,然后在验证这个结论:
dispatchTouchEvent()
和onInterceptTouchEvent()
- 返回true表示事件被拦截,不继续;
- 返回false表示事件不被拦截,继续下一步流程;
onTouchEvent()
- 返回true表示事件被处理了,不用传递给上一级视图;
- 返回false表示事件交给上一级视图处理;
初始情况下他们的默认返回值都为false
。
拦截onInterceptTouchEvent()
假设女班长暗恋帅气的我,自己偷偷帮我完成了任务,这时候事件就被女班长(MonitressViewGroup)的onInterceptTouchEvent()
方法拦截了,即MonitressViewGroup
的onInterceptTouchEvent()
返回ture,此时Log输出如下
整理视图如下:
同样的,也可以假设老师人比较好,不忍心麻烦学生,自己处理了,这个过程类似女班长处理过程。
到这里,我想大家对事件的分发、拦截已经有一个比较清楚的认识了。接下来我们来看下事件的处理。
处理onTouchEvent()
我们处理完任务后是需要将完成结果汇报给上级的,也就是帅气的我需要向我亲爱的女班长汇报结果,班长向老师汇报结果。假设我不能按时完成任务,没将任务结果汇报给女班长,也就是HandsomeMyView的onTouchEvent()
方法返回true(事件被处理了,不用返回给上级),此时Log输出如下,女班长和老师不用继续处理事件了
整理视图如下:
同样的,女班长和老师也可以不像他们各自的上级汇报,过程类似帅气的我
(HandsomeMyView)。
Ref
- Mastering the Android Touch System
- Android群英传
…