Android事件分发机制笔记——事件序列中不同行为的事件分发
一、什么是事件序列
事件序列是指手指触摸手机屏幕所产生的一系列行为。
在Android中这些行为都封装在MotionEvent中,下面列举一些行为 :
(加粗常见)
1. 单点触控的行为:
- ACTION_DOWN :手指按下屏幕的一瞬间,是事件序列的开始
- ACTION_UP :手指离开屏幕的瞬间,是事件序列的结束
- ACTION_MOVE :手指在屏幕移动
- ACTION_CANCEL :行为取消,可以视为UP事件,但不执行通常执行的任何操作。在事件被上层拦截时,上层View回收事件处理权的时候触发。
- ACTION_OUTSIDE :行为移动到控件之外的位置,不提供完整手势,仅提供移动/触摸的初始位置。在手指不在控件区域时触发。这个行为一般会在Dialog或者悬浮窗的场景触发。
2. 多点触控的行为:
在多点触控中,对多个手指进行了编号,第一次按下的手指特殊处理作为主指针,其余的作为辅助指针,接下来的行为就是为辅助指针设计的。(单点触控的行为依旧会触发,但是是主指针触发的)
- ACTION_POINTER_DOWN :非主要的手指按下屏幕
- ACTION_POINTER_UP :非主要的手指离开屏幕
3. 鼠标事件的行为:
ACTION_HOVER_ENTER、ACTION_HOVER_MOVE、ACTION_HOVER_EXIT、ACTION_SCROLL
事件序列就是以DOWN开头,UP结尾,中间有无数MOVE的一系列行为。
二、事件的传递规则
事件分发的顺序是一个老生常谈的点,也是面试几乎都会问到的知识,这里不主要讲事件分发的顺序,简单带一下。
事件分发的顺序:Activity -> Window(PhoneWindow实现) -> ViewGroup -> 子View
事件分发的顺序是一个U型的顺序,如果事件分发到最后依旧没有任何一个View消耗这个时间,最终这个事件会交给Activity的onTouchEvent来处理。
三、事件序列中不同行为的事件分发
上代码~
这里根据事件分发的顺序,封装了一个MyViewGroup,以及子View LoadView,观察它们的事件分发:
MyViewFroup :
public class MyViewGroup extends LinearLayout {
……
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(this.getClass().toString(), "onTouchEvent: "+getS(event));
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(this.getClass().toString(), "dispatchTouchEvent: "+getS(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d(this.getClass().toString(), "onInterceptTouchEvent: "+getS(ev));
return super.onInterceptTouchEvent(ev);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
……
private String getS(MotionEvent event){
String s= "";
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
s = "ACTION_DOWN";
break;
case MotionEvent.ACTION_MOVE:
s = "ACTION_MOVE";
break;
case MotionEvent.ACTION_UP:
s = "ACTION_UP";
break;
}
return s;
}
}
LoadingView :
public class LoadingView extends View {
……
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(this.getClass().toString(), "onTouchEvent: "+getS(event));
return super.onTouchEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(this.getClass().toString(), "dispatchTouchEvent: "+getS(event));
return super.dispatchTouchEvent(event);
}
private String getS(MotionEvent event){
String s= "";
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
s = "ACTION_DOWN";
break;
case MotionEvent.ACTION_MOVE:
s = "ACTION_MOVE";
break;
case MotionEvent.ACTION_UP:
s = "ACTION_UP";
break;
}
return s;
}
}
MainActivity :
public class MainActivity extends AppCompatActivity {
……
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(this.getClass().toString(), "dispatchTouchEvent: "+getS(ev));
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d(this.getClass().toString(), "onTouchEvent: "+getS(event));
return super.onTouchEvent(event);
}
private String getS(MotionEvent event){
String s= "";
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
s = "ACTION_DOWN";
break;
case MotionEvent.ACTION_MOVE:
s = "ACTION_MOVE";
break;
case MotionEvent.ACTION_UP:
s = "ACTION_UP";
break;
}
return s;
}
}
在代码中分别为Activity,ViewGroup,View的事件分发的方法都打上了log,接下来我们来看看整个事件的传递序列是怎样的。
1. 每个view都不处理事件
跑一下上面的代码,查看log:
image打出了一个小长串的log,我们分着来看一看。先看DOWN行为:
image可以看到,首先通过dispatchTouchEvent将事件按序依次分发:MainActivity -> MyViewGroup (ViewGroup的onInterceptTouchEvent用来判断是否拦截该事件)-> LoadingView
之后到达最底层的子View后依旧没有消耗事件,通过onTouchEvent依次返回:LoadingView -> MyViewGroup -> MainActivity,最后还是交还给了Activity的TouchEvent()
再来看看MOVE事件:
image可以看到,MOVE行为并没有继续向下传递,而是直接分发给了Activity,并且由Activity的onTouchEvent()处理。
再来看看UP事件:
imageUP行为和MOVE一样,直接交给了Activity的onTouchEvent()处理。
2.传递途中有View处理事件
可以看到,DOWN行为的传递终点决定了后续行为的传递终点,后续的行为会直接分发到终点进行处理。但是是不是只有不处理事件的时候是这样的?我们来看看:
我们在ViewGroup层对事件进行处理( onTouchEvent()返回true ):
image通过log可以看到,处理事件情况下的结论和上边的结果是一样的。当ViewGroup的onTouchEvent处理了事件之后,后续事件会直接分发到ViewGroup的onTouchEvent。
四、onInterceptTouchEvent和onTouchEvent对事件序列的不同影响
在上边的分析中主要以onTouchEvent处理事件分析事件序列的事件分发顺序,但是同样影响着事件分发顺序的onInterceptTouchEvent是否和onTouchEvent一致?
我们都知道在ViewGroup的onInterceptTouchEvent决定着是否拦截事件,它的默认值是false,在一些场景中我们也常通过重写onInterceptTouchEvent来解决一些事件的冲突,比较常见的就是滑动冲突。
打log分析:
将MyViewGroup的onInterceptTouchEvent返回值指定为true,拦截事件。
image首先来看DOWN行为:
image通过log可以看出来,在onInterceptTouchEvent拦截了事件之后,dispatchTouchEvent不会将事件继续向下传递,而是交给了MyViewGroup的onTouchEvent,MyViewGroup的onTouchEvent并没有处理这个事件,于是又向上传递事件,最终还是交给了Activity的onTouchEvent,事件的终点还是Activity的onTouchEvent。
再来看看MOVE和UP行为:
image看到log,一目了然,全是在Activity中处理的。说明onInterceptTouchEvent对事件的终点不产生影响。
总结一下:onInterceptTouchEvent对事件序列的影响只影响事件分发的路径并不影响事件分发的终点,最终事件分发到哪,还是由onTouchEvent是否处理这个事件决定
到这里,事件分发中比较详细的点就分析的差不多了,具体的事件分发机制的原理可以研究事件分发的源码,这里不多做赘述,事件序列的不同行为的事件分发顺序也是在面试中一个比较细的知识点,这篇笔记也算是之前自己在学习事件分发时候漏掉的细节补的坑~
学无止境,边走边补 _ (:з」∠) _