Android-你必须要懂得事件分发全解析
一、 事件分发的对象是谁?
答:事件
当用户触摸屏幕时(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进行传递并最终得到处理
即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
三、事件在哪些对象之间进行传递?
答:Activity、ViewGroup、View
image.png一个点击事件产生后,传递顺序是: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()
image.png五、 总结
Android事件分发机制的本质是要解决:
点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。
这里的对象是指Activity、ViewGroup、View
Android中事件分发顺序:Activity(Window) -> ViewGroup -> View
事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成
经过上述3个问题,相信大家已经对Android的事件分发有了感性的认知,接下来,我将详细介绍Android事件分发机制。
六、事件分发机制方法介绍
事件分发顺序.pngAndroid事件分发流程如下:(必须熟记)
Android事件分发顺序:Activity(Window) -> ViewGroup -> View
super:调用父类方法(在这里Activity的父类是ViewGroup,ViewGroup的父类是View)
true:消费事件,即事件不继续往下传递
false:不消费事件,事件也不继续往下传递 / 交由给父控件onTouchEvent()处理
看图总结:首先当你触摸的屏幕的时候会一口气产生至少2个事件,一个down,一个up,其余就是你手指移动过程中产生的move事件。首先down事件会先执行activity的dispatchTouchEvent方法,不管它返回true或者false都会直接在这里被消费,不会继续往下传递。只有调用父类方法的时候会执行ViewGroup的dispatchTouchEvent方法,此时假如dispatchTouchEvent返回true也代表在此消费,不会继续传递,假如返回false,表示不分发,则交还给Activity,说大家都处理不了,你自己处理吧。假如dispatchTouchEvent返回父类,则会调用ViewGroup的onInterceptTouchEvent,返回true表示拦截这个事件,交给自己的onTouchEvent方法消费事件,假如返回false,表示不拦截,则交给View的dispatchTouchEvent方法,同样的做处理,之后交给View的onTouchEvent方法,返回true代表消费,返回false,表示无法处理,交还给上级。
其实,这些不需要硬记,实在开发中遇到不知道哪里返回true,哪里返回false时,回来看看这个顺序。最主要的是多去用用就记住了。下面我们看一般的事件传递。
七、一般事件传递
image.png7.1 默认情况
即不对控件里的方法(dispatchTouchEvent()、onTouchEvent()、onInterceptTouchEvent())进行重写或更改返回值
那么调用的是这3个方法的默认实现:调用父类的方法
事件传递情况:
从Activity A---->ViewGroup B--->View C,从上往下调用dispatchTouchEvent()
再由View C--->ViewGroup B --->Activity A,从下往上调用onTouchEvent()
7.2处理事件
假设View C希望处理这个点击事件,即C被设置成可点击的(Clickable)或者覆写了C的onTouchEvent方法返回true。
事件传递情况:
DOWN事件被传递给C的onTouchEvent方法,该方法返回true,表示处理这个事件
因为C正在处理这个事件,那么DOWN事件将不再往上传递给B和A的onTouchEvent();
该事件列的其他事件(Move、Up)也将传递给C的onTouchEvent()
7.3 拦截DOWN事件
假设ViewGroup B希望处理这个点击事件,即B覆写了onInterceptTouchEvent()返回true、onTouchEvent()返回true。 事件传递情况:
DOWN事件被传递给B的onInterceptTouchEvent()方法,该方法返回true,表示拦截这个事件,即自己处理这个事件(不再往下传递)
调用onTouchEvent()处理事件(DOWN事件将不再往上传递给A的onTouchEvent())
该事件列的其他事件(Move、Up)将直接传递给B的onTouchEvent()
该事件列的其他事件(Move、Up)将不会再传递给B的onInterceptTouchEvent方法,该方法一旦返回一次true,就再也不会被调用了。
7.4 拦截DOWN的后续事件
假设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再也不会收到该事件列产生的后续事件。
八、源码分析
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方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。
如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。
下面看onTouchEvent(event)**的源码分析
主要这里面调用到了performClick()方法,关注这个方法即可
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
只要mOnClickListener不为null,就会去调用onClick方法;
那么,mOnClickListener又是在哪里赋值的呢?请继续看:
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
当我们通过调用setOnClickListener方法来给控件注册一个点击事件时,就会给mOnClickListener赋值(不为空),即会回调onClick()。
结论
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()
onTouch()和onTouchEvent()的区别
这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。