简介
在Android系统中,针对输入的事件由InputEvent来表示
针对由键盘输入的事件封装成KeyEvent来进行传递
针对View的点击和滑动的事件封装成MotionEvent来进行传递
今天我们要分析的就是封装好的MotionEvent的事件传递
MotionEvent
- 事件分为如下几种
ACTION_DOWN:按下事件,一个事件序列中有一个ACTION_DOWN
ACTION_MOVE:滑动事件,一个事件序列中至少有一个ACTION_MOVE
ACTION_UP:抬起事件,一个事件序列中有一个ACTION_UP
ACTION_CANCEL:取消事件,一般是非人为因素影响的
所以说一个事件序列中 包含一个ACTION_DOWN和一个ACTION_UP,中间有一个或者多个ACTION_MOVE - 在整个的事件分发中主要涉及到Activity、ViewGroup、View三个类
Activity作为用户层面事件的起源以及终结
ViewGroup和View作为UI的显示层以及事件的处理层
事件传递得顺序是 Activity ---> ViewGroup ----> View
整个事件分发过程中,涉及到的方法有 dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent(),在Activity、ViewGroup、View中对于上述三个都有不同的实现,对应关系如下表格
方法\控件 | Activity | ViewGroup | View |
---|---|---|---|
dispatchTouchEvent | 有 | 有 | 有 |
onInterceptTouchEvent | 无 | 有 | 无 |
onTouchEvent | 有 | 有 | 有 |
dispatchTouchEvent()
- 该方法的意思就是起到事件分发的作用;
- 当返回true就表示消费这个事件;
- 返回false,就表示不分发这个事件,转而将这个事件传递给父View的onTouchEvent();
- 返回 super.dispatchTouchEvent(),如果当前控件是ViewGroup的话会将事件传递给自己的onInterceptTouchEvent()方法,如果当前控件是View的话会将事件传递给自己的onTouchEvent()。
onInterceptTouchEvent()
- 该方法是起到事件拦截的作用,仅仅ViewGroup独有的,是在ViewGroup的dispatchTouchEvent()方法中调用的,Activity和View中没有此方法;
- 当返回true的话,表示要拦截当前这个事件,并将这个事件传递给自己的onTouchEvent方法来处理
- 放返回false的话,表示不拦截这个事件,如果当前ViewGroup没有子View能分发这个事件,就将这个事件传递给View的dispatchToucheEvent,否则的话将这个事件分发给子View的dispatchToucheEvent()方法
- 当调用super.onInterceptTouchEvent(),等同于返回false,ViewGroup默认onInterceptTouchEvent()返回false
onTouchEvent()
- 该方法是表示消费事件的处理方法
- 当返回true的话就表示要消费这个事件
- 当返回false的话,表示不消费这个事件,转而将这个事件传递给父View的onTouchEvent()方法。
requestDisallowInterceptTouchEvent
- 除了以上三个作为事件分发的基础,该方法也能影响事件的分发,该方法主要是子View用来通知父View下一个事件该怎么处理
- 如果设置为false,就告诉父View可以拦截这个事件,就不要传递给我了
- 如果设置为true,就告诉父View不要拦截,要把事件传递给子View。
代码实操
上面我们介绍了事件分发过程中涉及到的类以及方法,在下面我们通过继承ViewGroup和View,以及重写 dispatchTouchEvent(),onInterceptTouchEvent(),onTouchEvent()这三个方法打印一下事件,看下分发的流程。
情况一:我们自定义 MyGroupViewOne,MyGroupViewTwo以及MyViewOne,同时重写了事件分发的方法,返回super.xxxx()
class MyViewGroupOne :RelativeLayout {
private val TAG = "MyViewGroupOne"
constructor(context:Context):super(context)
constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet)
constructor(context: Context,attributeSet: AttributeSet,defStyleAttr:Int):super(context,attributeSet,defStyleAttr)
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
ev?.let {
//这里的MotionEventutils是一个封装好的工具类,专门用来打印对应的事件的一个方法
MotionEventUtils.printEvent(ev.action,"dispatchTouchEvent",TAG)
}
return super.dispatchTouchEvent(ev)
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
ev?.let {
MotionEventUtils.printEvent(ev.action,"onInterceptTouchEvent",TAG)
}
return super.onInterceptTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
MotionEventUtils.printEvent(event.action,"onTouchEvent",TAG)
}
return super.onTouchEvent(event)
}
}
MotionEventUtils的代码
同样MyViewGroupTwo如同MyViewGroupOne,我们看下MyViewOne
MyViewOne的代码
class MyViewOne : View {
private val TAG = "MyViewOne"
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(
context,
attributeSet,
defStyleAttr
)
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
event?.let {
MotionEventUtils.printEvent(event.action, "dispatchTouchEvent", TAG)
}
return super.dispatchTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
MotionEventUtils.printEvent(event.action, "onTouchEvent", TAG)
}
return super.onTouchEvent(event)
}
}
同时我们重写MainActivity的dispatchTouchEvent()和onTouchEvent()方法
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
ev?.let {
MotionEventUtils.printEvent(ev.action, "dispatchTouchEvent", TAG)
}
return super.dispatchTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
MotionEventUtils.printEvent(event.action, "onTouchEvent", TAG)
}
return super.onTouchEvent(event)
}
我们就是重写了方法,然后将对应的事件给打印出来了,我们看下打印的log
======================= Down ==========================
2020-12-06 10:14:48.328 14450-14450/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent -----> action_down
2020-12-06 10:14:48.330 14450-14450/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent -----> action_down
2020-12-06 10:14:48.330 14450-14450/com.dashingqi.module.event D/MyViewGroupOne: onInterceptTouchEvent -----> action_down
2020-12-06 10:14:48.331 14450-14450/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent -----> action_down
2020-12-06 10:14:48.331 14450-14450/com.dashingqi.module.event D/MyViewGroupTwo: onInterceptTouchEvent -----> action_down
2020-12-06 10:14:48.331 14450-14450/com.dashingqi.module.event D/MyViewOne: dispatchTouchEvent -----> action_down
2020-12-06 10:14:48.332 14450-14450/com.dashingqi.module.event D/MyViewOne: onTouchEvent -----> action_down
2020-12-06 10:14:48.332 14450-14450/com.dashingqi.module.event D/MyViewGroupTwo: onTouchEvent -----> action_down
2020-12-06 10:14:48.333 14450-14450/com.dashingqi.module.event D/MyViewGroupOne: onTouchEvent -----> action_down
2020-12-06 10:14:48.335 14450-14450/com.dashingqi.module.event D/MainActivity: onTouchEvent -----> action_down
======================= Move和Up ==========================
2020-12-06 10:47:46.204 18012-18012/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent -----> action_move
2020-12-06 10:47:46.204 18012-18012/com.dashingqi.module.event D/MainActivity: onTouchEvent -----> action_move
2020-12-06 10:47:46.205 18012-18012/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent ------> action_up
2020-12-06 10:47:46.205 18012-18012/com.dashingqi.module.event D/MainActivity: onTouchEvent ------> action_up
我们可以看到Down事件从Activity传递到MyViewOne,经过MyViewOne的onTouchEvent 层层传递到Activity的onTouchEvent()。在这过程中 ViewGroup没有消费也没有拦截,View也没有消费最终传递给了Activity的onTouchEvent()方法来处理了。
MOVE和UP事件只是在Activity中传递了一下,并没有传递到ViewGroup和View中,这里留一个悬念,留作在代码分析中解释一下。
Activity # onTouchEvent()
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
Activity的onTouchEvent()方法被调用,是在它之上的View都不能处理这个事件,就会回传给它,
当处于窗口之外的触摸事件,View也是不能处理的,因为根本就不在坐标范围内嘛。
可以看到,onTouchEvent中调用了 Window的 shouldCloserOnTouch
Window # shouldCloserOnTouch()
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
// 当事件是ACTION_UP并且不在窗口之内 isOutside 为true
// 当isOutside为true 是该方法返回true的其中一个条件
final boolean isOutside =
event.getAction() == MotionEvent.ACTION_UP && isOutOfBounds(context, event)
|| event.getAction() == MotionEvent.ACTION_OUTSIDE;
if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
return true;
}
return false;
}
情况二:我们让MyViewOne主动消费一下Down的事件,让它的onTouchEvent返回true
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
MotionEventUtils.printEvent(event.action, "onTouchEvent", TAG)
}
return true
}
我们在MOVE事件到来的时候进行一下拦截,我们看下打印log
======================= Down ==========================
2020-12-06 10:52:12.587 19654-19654/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent -----> action_down
2020-12-06 10:52:12.589 19654-19654/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent -----> action_down
2020-12-06 10:52:12.589 19654-19654/com.dashingqi.module.event D/MyViewGroupOne: onInterceptTouchEvent -----> action_down
2020-12-06 10:52:12.589 19654-19654/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent -----> action_down
2020-12-06 10:52:12.589 19654-19654/com.dashingqi.module.event D/MyViewGroupTwo: onInterceptTouchEvent -----> action_down
2020-12-06 10:52:12.590 19654-19654/com.dashingqi.module.event D/MyViewOne: dispatchTouchEvent -----> action_down
2020-12-06 10:52:12.590 19654-19654/com.dashingqi.module.event D/MyViewOne: onTouchEvent -----> action_down
======================= Move和Up ==========================
2020-12-06 10:52:12.648 19654-19654/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent -----> action_move
2020-12-06 10:52:12.649 19654-19654/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent -----> action_move
2020-12-06 10:52:12.649 19654-19654/com.dashingqi.module.event D/MyViewGroupOne: onInterceptTouchEvent -----> action_move
2020-12-06 10:52:12.649 19654-19654/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent -----> action_move
2020-12-06 10:52:12.649 19654-19654/com.dashingqi.module.event D/MyViewGroupTwo: onInterceptTouchEvent -----> action_move
2020-12-06 10:52:12.650 19654-19654/com.dashingqi.module.event D/MyViewOne: dispatchTouchEvent -----> action_move
2020-12-06 10:52:12.650 19654-19654/com.dashingqi.module.event D/MyViewOne: onTouchEvent -----> action_move
2020-12-06 10:52:12.840 19654-19654/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent ------> action_up
2020-12-06 10:52:12.841 19654-19654/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent ------> action_up
2020-12-06 10:52:12.841 19654-19654/com.dashingqi.module.event D/MyViewGroupOne: onInterceptTouchEvent ------> action_up
2020-12-06 10:52:12.841 19654-19654/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent ------> action_up
2020-12-06 10:52:12.841 19654-19654/com.dashingqi.module.event D/MyViewGroupTwo: onInterceptTouchEvent ------> action_up
2020-12-06 10:52:12.841 19654-19654/com.dashingqi.module.event D/MyViewOne: dispatchTouchEvent ------> action_up
2020-12-06 10:52:12.841 19654-19654/com.dashingqi.module.event D/MyViewOne: onTouchEvent ------> action_up
可以看到事件Down传递到了MyViewOne中并且被消费了,同时MOVE和UP事件夜传递到了MyViewGroup和MyView中,并且被MyViewOne哥i消费调了,
其实上述现象,可以将MyViewOne android:clickable="true" 设置为true 也没能看到上述现象
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
MotionEventUtils.printEvent(event.action, "onTouchEvent", TAG)
}
return super.onTouchEvent(event)
}
这里也留个悬念,留作在源码分析的时候进行讲解
情况三:我们改造一下MyViewGroupTwo的onInterceptTouchEvent(),同时保留MyViewOne的clickable = true
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
ev?.let {
MotionEventUtils.printEvent(ev.action, "onInterceptTouchEvent", TAG)
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
return false
}
MotionEvent.ACTION_MOVE -> {
//拦截事件
return true
}
MotionEvent.ACTION_UP -> {
return true
}
else ->
return false
}
}
return false
}
======================= Down ==========================
2020-12-06 11:00:53.614 20373-20373/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent -----> action_down
2020-12-06 11:00:53.615 20373-20373/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent -----> action_down
2020-12-06 11:00:53.616 20373-20373/com.dashingqi.module.event D/MyViewGroupOne: onInterceptTouchEvent -----> action_down
2020-12-06 11:00:53.616 20373-20373/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent -----> action_down
2020-12-06 11:00:53.616 20373-20373/com.dashingqi.module.event D/MyViewGroupTwo: onInterceptTouchEvent -----> action_down
2020-12-06 11:00:53.616 20373-20373/com.dashingqi.module.event D/MyViewOne: dispatchTouchEvent -----> action_down
2020-12-06 11:00:53.617 20373-20373/com.dashingqi.module.event D/MyViewOne: onTouchEvent -----> action_down
======================= Move 和 Up ==========================
2020-12-06 11:00:53.690 20373-20373/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent -----> action_move
2020-12-06 11:00:53.691 20373-20373/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent -----> action_move
2020-12-06 11:00:53.691 20373-20373/com.dashingqi.module.event D/MyViewGroupOne: onInterceptTouchEvent -----> action_move
2020-12-06 11:00:53.691 20373-20373/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent -----> action_move
2020-12-06 11:00:53.691 20373-20373/com.dashingqi.module.event D/MyViewGroupTwo: onInterceptTouchEvent -----> action_move
2020-12-06 11:00:53.691 20373-20373/com.dashingqi.module.event D/MyViewOne: dispatchTouchEvent -----> action_cancel
2020-12-06 11:00:53.691 20373-20373/com.dashingqi.module.event D/MyViewOne: onTouchEvent -----> action_cancel
2020-12-06 11:00:53.714 20373-20373/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent -----> action_move
2020-12-06 11:00:53.714 20373-20373/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent -----> action_move
2020-12-06 11:00:53.714 20373-20373/com.dashingqi.module.event D/MyViewGroupOne: onInterceptTouchEvent -----> action_move
2020-12-06 11:00:53.714 20373-20373/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent -----> action_move
2020-12-06 11:00:53.714 20373-20373/com.dashingqi.module.event D/MyViewGroupTwo: onTouchEvent -----> action_move
2020-12-06 11:00:53.715 20373-20373/com.dashingqi.module.event D/MainActivity: onTouchEvent -----> action_move
2020-12-06 11:00:53.907 20373-20373/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent ------> action_up
2020-12-06 11:00:53.907 20373-20373/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent ------> action_up
2020-12-06 11:00:53.908 20373-20373/com.dashingqi.module.event D/MyViewGroupOne: onInterceptTouchEvent ------> action_up
2020-12-06 11:00:53.908 20373-20373/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent ------> action_up
2020-12-06 11:00:53.908 20373-20373/com.dashingqi.module.event D/MyViewGroupTwo: onTouchEvent ------> action_up
2020-12-06 11:00:53.909 20373-20373/com.dashingqi.module.event D/MainActivity: onTouchEvent ------> action_up
这里我们重点分析一下 第一个Move事件到来时,MyViewGroupTwo拦截了这个事件,并且MyViewOne 中dispatchTouchEvent和onTouchEvent中都收到了 cancel事件
当第二个Move以及之后的Move事件到达的时候,MyViewGroupTwo同样拦截了,此时调用了自己的onTouchEvent,由于自己的onTouchEvent并没有消费这个事件,所以将事件向上传递,但是并没有回传给MyViewGroupOne的onTouchEvent而是Activity的onTouchEvent,其实这个地方我的理解就是,第一个Move事件 MyViewGroupOne没有拦截也就是不会将事件传递给自己的onTouchEvent方法处理,那么该序列中的其他Move事件到达时都不会传递给onTouchEVent方法处理,所以回传时就不会交给它来处理了。同样Up事件也是同理的。
情况四:我们在MyViewGroupOne的onTouchEvent方法中使用requestDisallowInterceptTouchEvent(true),同时保持情况三的条件
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
MotionEventUtils.printEvent(event.action, "onTouchEvent", TAG)
when(event.action){
MotionEvent.ACTION_DOWN ->{
parent.requestDisallowInterceptTouchEvent(true)
}
}
}
return super.onTouchEvent(event)
}
在onTouchEvent() 的Down事件中 我们通知父View 当第一个Move事件到来时,不要拦截 要传递给我。
======================= Down ==========================
2020-12-06 11:23:33.468 21432-21432/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent -----> action_down
2020-12-06 11:23:33.468 21432-21432/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent -----> action_down
2020-12-06 11:23:33.468 21432-21432/com.dashingqi.module.event D/MyViewGroupOne: onInterceptTouchEvent -----> action_down
2020-12-06 11:23:33.468 21432-21432/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent -----> action_down
2020-12-06 11:23:33.468 21432-21432/com.dashingqi.module.event D/MyViewGroupTwo: onInterceptTouchEvent -----> action_down
2020-12-06 11:23:33.468 21432-21432/com.dashingqi.module.event D/MyViewOne: dispatchTouchEvent -----> action_down
2020-12-06 11:23:33.469 21432-21432/com.dashingqi.module.event D/MyViewOne: onTouchEvent -----> action_down
======================= Move 和 Up ==========================
2020-12-06 11:23:33.517 21432-21432/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent -----> action_move
2020-12-06 11:23:33.517 21432-21432/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent -----> action_move
2020-12-06 11:23:33.518 21432-21432/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent -----> action_move
2020-12-06 11:23:33.518 21432-21432/com.dashingqi.module.event D/MyViewOne: dispatchTouchEvent -----> action_move
2020-12-06 11:23:33.518 21432-21432/com.dashingqi.module.event D/MyViewOne: onTouchEvent -----> action_move
2020-12-06 11:23:33.701 21432-21432/com.dashingqi.module.event D/MainActivity: dispatchTouchEvent ------> action_up
2020-12-06 11:23:33.702 21432-21432/com.dashingqi.module.event D/MyViewGroupOne: dispatchTouchEvent ------> action_up
2020-12-06 11:23:33.702 21432-21432/com.dashingqi.module.event D/MyViewGroupTwo: dispatchTouchEvent ------> action_up
2020-12-06 11:23:33.702 21432-21432/com.dashingqi.module.event D/MyViewOne: dispatchTouchEvent ------> action_up
2020-12-06 11:23:33.702 21432-21432/com.dashingqi.module.event D/MyViewOne: onTouchEvent ------> action_up
通过日志我们看到,当MOVE和Up事件到达时候,MyViewGroup并没有执行onInterceptTouchEvent方法,直接跳过它了,这就是requestDisallowInterceptTouchEvent()的作用效果
好接下来我们就通过源码开进一步揭开事件分发的神秘面纱,也能针对上述的情况做一下分析。
源码解析
Android的事件经过 系统进程的处理过程会传递到 应用进程的 Activity中 进而 向下传递,接下来我们就看下 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);
}
当DOWN事件到来的时候,回调用到 onUserInteraction(),该方式是一个空实现,当你的手机按住home 会回调这个方法当 getWindow().superDispatchToucheEvent() 返回true ,该方法返回true,否则的话 就调用 onTouchEvent()方法来处理这个事件。
getWindow(),superDispatchTouchEvent()
- getWindow()获取到的是一个 Window对象,Window是一个抽象类,A android中它的唯一实现子类是PhoneWindow()
PhoneWindow # superDispatchTouchEvent()
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
- 其中 mDecor是DecorView类型的,DecorView是作为我们设置布局的父布局,继承FrameLayout
Decorview # superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
内部调用了父类的 dispatchTouchEvent(),FrameLayout作为DecorView的父类,内部没有实现dispatchTouchEvent(),而它的父类ViewGroup内部实现了 dispatchTouchEvent()
这也是 Activity将事件传递到ViewGrouop的流程
ViewGroup # dispatchTouchEvent()
ViewGroup的dispatchTouchEvent()方法还是很长的,我们这里通过代码的功能拆分一下同时我们可以发现 该方法返回值是 handled 默认是 false,该方法最重要的逻辑都在 这个if条件成立的情况下
if (onFilterTouchEventForSecurity(ev)){.....}
onFilterTouchEventForSecurity()
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//noinspection RedundantIfStatement
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
return false;
}
return true;
}
该方法主要是用来检查当前控件是否被遮蔽了,隐藏了,如果是的话就返回false,否则就返回true
当返回false,dispatchTouchEvent()就返回false,就不做事件的分发了。
然后我们走进了if语句中了,就下来第一个功能代码块如下
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
当前是Down事件的话,就执行 resetTouchState()
private void resetTouchState() {
//清空链表
clearTouchTargets();
resetCancelNextUpFlag(this);
//重置了 mGroupFlags 的状态
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
//内部执行了 clearTouchTaegets()
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
//mFirstTouchTarget变量是指向了 链表的第一个节点
// mFirstTouchTarget 是TouchTarget类型的
mFirstTouchTarget = null;
}
}
当Down事件来的时候,会作为一个事件序列的开始,它会把事件链表给清空,同时将mFirstTouchTarget变量置为null;并且会把mGroupFlags的状态重置,默认 mGroupFlags为0,mGroupFlags变量值会直接影响 内部onInterceptTouchEvent方法的执行
谈下 TouchTarget
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
//用来记录最近一次被回收的TouchTarget,用于复用
private static TouchTarget sRecycleBin;
// 被回收的次数
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// 当前最近一次消费事件的子View
@UnsupportedAppUsage
public View child;
// The combined bit mask of pointer ids for all pointers captured by the target.
public int pointerIdBits;
// The next target in the target list.
public TouchTarget next;
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
public void recycle() {
if (child == null) {
throw new IllegalStateException("already recycled once");
}
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin;
sRecycleBin = this;
sRecycledCount += 1;
} else {
next = null;
}
child = null;
}
}
- TouchTarget内部是一个链表的数据结构,有两个方法 obtain 和recycle
- obtain()方法用于存储当前消费事件的View,并将它置于链表的头部
- recycle 用于回收链表头部的View,将它置为null
- 我们的mFirstTouchTarget就是指向了链表的头部,内部的child就是本次事件序列中事件的消费View
接下来就是决定 onInterceptTouchEvent事件是否执行的代码块了
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;
}
当是Down事件或者 上一次的事件被子View消费了就会进入这个if条件语句中;mGroupFlags和 FlAG_DISALLOW_INTERCEPT 进行与运算 ;当是DOWN事件的话 disallowIntercept 为false,就会执行onInterceptTouchEvent方法 否则的话 intercepted 赋值为false;当既不是Down事件又没有子View消费事件的话就将 intercepted赋值为true。
看下 onInterceptTouchEvent()
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
正常情况下 onInterceptTouchEvent就是返回true的;在执行完上述代码块之后 如果不决定拦截的话就开始决定分发给哪个子View了
遍历子View,寻找符合条件的子View进行事件的分发
if (!canceled && !intercepted) {
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//分析1
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;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
final ArrayList preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 分析2
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 分析2.1
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//分析3
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();
//分析4
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
能走到这个代码块说明 不会做拦截的操作。
分析1:当是Down事件或者是MOVE事件的话 就进入到代码块中
分析2:此时会遍历子View,然后去执行2.1的逻辑
分析2.1 在这个地方回去验证当前的View是否符合分发的条件,符合分发的条件是
当前的View没有做动画
并且当事件的区域是在该子View的区域中
分析3:此时会调用dispatchTransformedTouchEvent()方法,将符合条件的View传进去
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
// 。。。。。。。省略之前的逻辑判断
if (child == null) {
//当child为null的时候,就调用ViewGroup的dispatchTouchEvent 也就是View的dispatchTouchEvent()
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//child不为空的时候 调用了View的dispatchTouchEvent()
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
所以当符合条件的View 不管是否为null 都会将是将传递给View的dispatchTouchEvent()方法进行处理;这也是事件从ViewGroup传递到View的dispatchTouchEvent
在分析View的dispatchTouchEvent方法之前我们看下分析4 看下mFirstTouchTarget变量的处理
分析4:此时调用了addTouchTarget()
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
能走到这个方法说明 子View的dispatchTouchEvent方法已经返回true说明要消费它了
该方法内部调用了obtain()创建了一个TouchTarget的变量,它的child指向了消费该事件的View
同时将mFirstTouchTarget指向了这个链表头部的TouchTarget
还记得上文中情况4的吗,我们在子View中使用 requestDisallowInterceptTouchEvent(true)使得ViewGroup中的onInterceptTouchEvent()方法并没有执行,我们看下该方法
requestDisallowInterceptTouchEvent
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);
}
}
该方法是就是子View用来告知父View下一个事件要不要拦截的作用
- 当disallowIntercept = true的时候,说明告知父View不要拦截,此时mGroupFlags == FLAG_DISALLOW_INTERCEPT
- 此时上文中的 disallowIntercept == true 就不会去执行onInterceptTouchEvent
- 当disallowIntercept = false的时候,告知父View下个事件不管你怎么处理 都不要传递给我吧 此时mGroup == 0
- 此时上文中的disallowIntercept == false,会走自己的onInterceptTouchEvent方法。
- 然后在从该ViewGroup 告知上一个ViewGroup 子View的态度
View # dispatchTouchEvent()
- View的dispatchTouchEvent()方法相对来说就比较简单,我们看下
public boolean dispatchTouchEvent(MotionEvent event) {
//..... 省略了一些逻辑判断
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//分析1
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//分析2
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
//结果返回的是result值
return result;
}
分析1: 当给控件设置了点击事件,并且当前控件是可用的 并且 给控件设置了 ToucheListener 并且onTouch返回true,那么 result为true。
分析2:当result为false 并且 onTouchEvent()为true,那么就返回true,那么就消费掉这个事件了。
可以看的出来,当给某一个控件设置了触摸事件 并且 onTouchEvent返回true,那么onTouchEvent方法就不会被执行了,发挥false 没啥事。
View # onTouchEvent()
通过源码我们可以看到 View的onTouchEvent默认是返回false的。
public boolean onTouchEvent(MotionEvent event) {
//分析1
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//分析2
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
return clickable;
}
//分析3
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
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) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
//分析4:
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
final int motionClassification = event.getClassification();
final boolean ambiguousGesture =
motionClassification == MotionEvent.CLASSIFICATION_AMBIGUOUS_GESTURE;
int touchSlop = mTouchSlop;
if (ambiguousGesture && hasPendingLongPressCallback()) {
if (!pointInView(x, y, touchSlop)) {
// The default action here is to cancel long press. But instead, we
// just extend the timeout here, in case the classification
// stays ambiguous.
removeLongPressCallback();
long delay = (long) (ViewConfiguration.getLongPressTimeout()
* mAmbiguousGestureMultiplier);
// Subtract the time already spent
delay -= event.getEventTime() - event.getDownTime();
checkForLongClick(
delay,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
touchSlop *= mAmbiguousGestureMultiplier;
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, touchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
return false
}
分析1:当控件是可点击的( clickable = “true”或者设置了点击事件,比如Button默认即使clickable的,TextView就不是),或者长按点击事件是true 那么 clickable = true
分析2: 当控件是不可用的话,直接将 clickable值返回,但是不做任何事件的响应。
分析3:当控件是可点击的话,然后根据不同的事件,做出不同的处理,并且只要满足这个条件那么onTouchEvent就返回true,那么就表明消费了这个事件。
这时候就可以解释给MyViewOne 设置成了 clickable =“true”属性之后 能接受到 MOVE和Up事件了,因为此时表明DOWN事件被MyViewOne给消费了,那么mFirstTouchTarget不为null,那么下一个事件下发的时候就会根据条件 分发到了当前能消费事件的View中。
-
分析4: performClick()
public boolean performClick() { notifyAutofillManagerOnClick(); final boolean result; //分析5 final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); // 当给控件设置了点击事件并且当前事件是可用的,那么就会调用onClick() 并且将 result 设置为true li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result; } //设置点击事件 public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; } ListenerInfo getListenerInfo() { if (mListenerInfo != null) { return mListenerInfo; } // 创建了ListenerInfo的对象并赋值给了 mListenerInfo mListenerInfo = new ListenerInfo(); return mListenerInfo; }
分析5: 这里将mListenerInfo 赋值给了 li ,mListenerInfo是在setOnclickListener()时候赋值的
可以看出 要想调用到你设置的点击事件,首先当前控件是可点击的并且是可用的,然后如果设置了触摸事件,那么onTouch()不能返回true,然后才会调用到onTouchEvent()方法,继而在UP事件到来的时候会去回调onClick方法。
总结
- 上面copy了这么多源代码下面就以一张图来表明一下事件分发的流程
- 以上是一Down事件为基础做了一次事件分发的原理分析
- 一个事件的分发可以看作递归的流程,事件从Activity进行下发,属于递的流程,如果当中有任何一个View将事件进行了消费那么就提前结束了递的流程,
- 如果到头来没有任何View对这个事件有意思,就开始的归的流程依次调用onTouchEvent方法,如果能消费就消费,不能的话就归到Activity的onTouchEvent方法中,
Move和Up事件的流程解析
- 针对开头列出的几种情况,我们看下 Move和Up的事件递归流程
情况1
情况2
情况3
情况4
千年不变的总结:
- 在递的流程中,如果是ViewGroup拦截了Down事件,那么Move和Up只能到达这个ViewGroup,要不要归还要看自己的onTouchEvent能不能消费 能的话就不归了,不能就开启归的流程直到能消费这个事件为止。
- 如果是View消费这个Down事件,同样Move和Up能达到View,在View中进行消费
- 如果重Activity ---> 到ViewGroup ----> View 都没有消费Down事件,那么Down会走完整个递归的流程,而Move和Up直接从Activity的dispatchTouch 传递到onTouchEvent中进行处理了。
- 本文中练习的代码