答:事件
当用户触摸屏幕时(View或ViewGroup派生的控件),将产生点击事件(Touch事件)。
Touch事件相关细节(发生触摸的位置、时间、历史记录、手势动作等)被封装成MotionEvent对象
主要发生的Touch事件有如下四种:
MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
MotionEvent.ACTION_MOVE:滑动View
MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
MotionEvent.ACTION_UP:抬起View(与DOWN对应)
事件列:从手指接触屏幕至手指离开屏幕,这个过程产生的一系列事件 任何事件列都是以DOWN事件开始,UP事件结束,中间有无数的MOVE事件,如下图:
即当一个MotionEvent 产生后,系统需要把这个事件传递给一个具体的 View 去处理.
答:将点击事件(MotionEvent)向某个View进行传递并最终得到处理
即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
答:Activity、ViewGroup、View
一个点击事件产生后,传递顺序是:Activity(Window) -> ViewGroup -> View
Android的UI界面是由Activity、ViewGroup、View及其派生类组合而成的。
View是所有UI组件的基类
一般Button、ImageView、TextView等控件都是继承父类View
ViewGroup是容纳UI组件的容器,即一组View的集合(包含很多子View和子VewGroup),
其本身也是从View派生的,即ViewGroup是View的子类
是Android所有布局的父类或间接父类:项目用到的布局(LinearLayout、RelativeLayout等),都继承自ViewGroup,即属于ViewGroup子类。
与普通View的区别:ViewGroup实际上也是一个View,只不过比起View,它多了可以包含子View和定义布局参数的功能。
答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()
Android事件分发机制的本质是要解决:
点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。
这里的对象是指Activity、ViewGroup、View
Android中事件分发顺序:Activity(Window) -> ViewGroup -> View
事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成
经过上述3个问题,相信大家已经对Android的事件分发有了感性的认知,接下来,我将详细介绍Android事件分发机制。
事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成,如下图:
Android事件分发流程如下:(必须熟记)
Android事件分发顺序:Activity(Window) -> ViewGroup -> View
其中:
super:调用父类方法
true:消费事件,即事件不继续往下传递
false:不消费事件,事件也不继续往下传递 / 交由给父控件onTouchEvent()处理
接下来,我将详细介绍这3个方法及相关流程。
消费事件
事件不会往下传递
后续事件(Move、Up)会继续分发到该View
3. 返回false
不消费事件
事件不会往下传递
将事件回传给父控件的onTouchEvent()处理
Activity例外:返回false=消费事件
后续事件(Move、Up)会继续分发到该View(与onTouchEvent()区别)
1. 返回true
自己处理(消费)该事情
事件停止传递
该事件序列的后续事件(Move、Up)让其处理;
2. 返回false(同默认实现:调用父类onTouchEvent())
不处理(消费)该事件
事件往上传递给父控件的onTouchEvent()处理
当前View不再接受此事件列的其他事件(Move、Up);
下面将用一段伪代码来阐述上述三个方法的关系和点击事件传递规则
// 点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
//代表是否消耗事件
boolean consume = false;
if (onInterceptTouchEvent(ev)) {
//如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
//则该点击事件则会交给当前View进行处理
//即调用onTouchEvent ()方法去处理点击事件
consume = onTouchEvent (ev) ;
} else {
//如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
//则该点击事件则会继续传递给它的子元素
//子元素的dispatchTouchEvent()就会被调用,重复上述过程
//直到点击事件被最终处理为止
consume = child.dispatchTouchEvent (ev) ;
}
return consume;
}
一般的事件传递场景有:
默认情况
处理事件
拦截DOWN事件
拦截后续事件(MOVE、UP)
即不对控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())进行重写或更改返回值
那么调用的是这3个方法的默认实现:调用父类的方法
事件传递情况:
从Activity A---->ViewGroup B—>View C,从上往下调用dispatchTouchEvent()
再由View C—>ViewGroup B —>Activity A,从下往上调用onTouchEvent()
注:虽然ViewGroup B的onInterceptTouchEvent方法对DOWN事件返回了false,后续的事件(MOVE、UP)依然会传递给它的onInterceptTouchEvent()
这一点与onTouchEvent的行为是不一样的。
假设View C希望处理这个点击事件,即C被设置成可点击的(Clickable)或者覆写了C的onTouchEvent方法返回true。
最常见的:设置Button按钮来响应点击事件
事件传递情况:
DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理这个事件
因为C正在处理这个事件,那么DOWN事件将不再往上传递给B和A的onTouchEvent();
该事件列的其他事件(Move、Up)也将传递给C的onTouchEvent()
假设ViewGroup B希望处理这个点击事件,即B覆写了onInterceptTouchEvent()返回true、onTouchEvent()返回true。 事件传递情况:(如下图)
DOWN事件被传递给B的onInterceptTouchEvent()方法,该方法返回true,表示拦截这个事件,即自己处理这个事件(不再往下传递)
调用onTouchEvent()处理事件(DOWN事件将不再往上传递给A的onTouchEvent())
该事件列的其他事件(Move、Up)将直接传递给B的onTouchEvent()
该事件列的其他事件(Move、Up)将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
假设ViewGroup B没有拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件。
DOWN事件传递到C的onTouchEvent方法,返回了true。
在后续到来的MOVE事件,B的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给B;这个MOVE事件将会被系统变成一个CANCEL事件传递给C的onTouchEvent方法
后续又来了一个MOVE事件,该MOVE事件才会直接传递给B的onTouchEvent()
后续事件将直接传递给B的onTouchEvent()处理
后续事件将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
C再也不会收到该事件列产生的后续事件。
特别注意:
如果ViewGroup A 拦截了一个半路的事件(如MOVE),这个事件将会被系统变成一个CANCEL事件并传递给之前处理该事件的子View;
该事件不会再传递给ViewGroup A的onTouchEvent()
只有再到来的事件才会传递到ViewGroup A的onTouchEvent()
所以,要想充分理解Android分发机制,本质上是要理解:
Activity对点击事件的分发机制
ViewGroup对点击事件的分发机制
View对点击事件的分发机制
4.1.2 汇总
当一个点击事件发生时,调用顺序如下
事件最先传到Activity的dispatchTouchEvent()进行事件分发
调用Window类实现类PhoneWindow的superDispatchTouchEvent()
调用DecorView的superDispatchTouchEvent()
最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent()
4.1.3 结论
当一个点击事件发生时,事件最先传到Activity的dispatchTouchEvent()进行事件分发,最终是调用了ViewGroup的dispatchTouchEvent()方法
这样事件就从 Activity 传递到了 ViewGroup
当点击Button时,执行Button的onClick(),但ViewGroupLayout注册的onTouch()不会执行
只有点击空白区域时才会执行ViewGroupLayout的onTouch();
结论:Button的onClick()将事件消费掉了,因此事件不会再继续向下传递。
结论
Android事件分发是先传递到ViewGroup,再由ViewGroup传递到View
在ViewGroup中通过onInterceptTouchEvent()对事件传递进行拦截
onInterceptTouchEvent方法返回true代表拦截事件,即不允许事件继续向子View传递;
返回false代表不拦截事件,即允许事件继续向子View传递;(默认返回false)
子View中如果将传递的事件消费掉,ViewGroup中将无法接收到任何事件。
View中dispatchTouchEvent()的源码分析
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
从上面可以看出:
只有以下三个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent(event)方法
第一个条件:mOnTouchListener != null;
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED;
第三个条件:mOnTouchListener.onTouch(this, event);
下面,我们来看看下这三个判断条件:
第一个条件:mOnTouchListener!= null
//mOnTouchListener是在View类下setOnTouchListener方法里赋值的 public void
setOnTouchListener(OnTouchListener l) {//即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
mOnTouchListener = l; }
第二个条件:(mViewFlags & ENABLED_MASK) == ENABLED
该条件是判断当前点击的控件是否enable
由于很多View默认是enable的,因此该条件恒定为true
第三个条件:mOnTouchListener.onTouch(this, event)
回调控件注册Touch事件时的onTouch方法
//手动调用设置
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
…
结论
onTouch()的执行高于onClick()
每当控件被点击时:
如果在回调onTouch()里返回false,就会让dispatchTouchEvent方法返回false,那么就会执行onTouchEvent();如果回调了setOnClickListener()来给控件注册点击事件的话,最后会在performClick()方法里回调onClick()。
onTouch()返回false(该事件没被onTouch()消费掉) = 执行onTouchEvent() = 执行OnClick()
如果在回调onTouch()里返回true,就会让dispatchTouchEvent方法返回true,那么将不会执行onTouchEvent(),即onClick()也不会执行;
onTouch()返回true(该事件被onTouch()消费掉) = dispatchTouchEvent()返回true(不会再继续向下传递) = 不会执行onTouchEvent() = 不会执行OnClick()
这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。
特别注意:请看下面代码
//&&为短路与,即如果前面条件为false,将不再往下执行 //所以,onTouch能够得到执行需要两个前提条件: //1.
mOnTouchListener的值不能为空 //2. 当前点击的控件必须是enable的。 mOnTouchListener !=
null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)
因此如果你有一个控件是非enable的,那么给它注册onTouch事件将永远得不到执行。对于这一类控件,如果我们想要监听它的touch事件,就必须通过在该控件中重写onTouchEvent方法来实现。
如果给控件注册了Touch事件,每次点击都会触发一系列action事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP等)
当dispatchTouchEvent在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到后一个事件(ACTION_MOVE和ACTION_UP)
即如果在执行ACTION_DOWN时返回false,后面一系列的ACTION_MOVE和ACTION_UP事件都不会执行
从上面对事件分发机制分析知:
dispatchTouchEvent()和 onTouchEvent()消费事件、终结事件传递(返回true)
而onInterceptTouchEvent 并不能消费事件,它相当于是一个分叉口起到分流导流的作用,对后续的ACTION_MOVE和ACTION_UP事件接收起到非常大的作用
请记住:接收了ACTION_DOWN事件的函数不一定能收到后续事件(ACTION_MOVE、ACTION_UP)
这里给出ACTION_MOVE和ACTION_UP事件的传递结论:
如果在某个对象(Activity、ViewGroup、View)的dispatchTouchEvent()消费事件(返回true),那么收到ACTION_DOWN的函数也能收到ACTION_MOVE和ACTION_UP
黑线:ACTION_DOWN事件传递方向 红线:ACTION_MOVE和ACTION_UP事件传递方向
如果在某个对象(Activity、ViewGroup、View)的onTouchEvent()消费事件(返回true),那么ACTION_MOVE和ACTION_UP的事件从上往下传到这个View后就不再往下传递了,而直接传给自己的onTouchEvent()并结束本次事件传递过程。
黑线:ACTION_DOWN事件传递方向 红线:ACTION_MOVE和ACTION_UP事件传递方向
ref:
https://lrh1993.gitbooks.io/android_interview_guide/content/android/basis/Event-Dispatch.html#三、事件分发场景介绍