1. 基础认知
1.1 事件分发的对象是谁?
答:事件
-
当用户触摸屏幕时(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 去处理,
1.2 事件分发的本质
答:将点击事件(MotionEvent)向某个View进行传递并最终得到处理
即当一个点击事件发生后,系统需要将这个事件传递给一个具体的View去处理。这个事件传递的过程就是分发过程。
1.3 事件在哪些对象之间进行传递?
答: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和定义布局参数的功能。
1.4 事件分发过程由哪些方法协作完成?
答:dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()
下文会对这3个方法进行详细介绍
1.5 总结
- Android事件分发机制的本质是要解决:点击事件由哪个对象发出,经过哪些对象,最终达到哪个对象并最终得到处理。
这里的对象是指Activity、ViewGroup、View
- Android中事件分发顺序:Activity(Window) -> ViewGroup -> View
- 事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成
经过上述3个问题,相信大家已经对Android的事件分发有了感性的认知,接下来,我将详细介绍Android事件分发机制。
2. 事件分发机制方法&流程介绍
- 事件分发过程由dispatchTouchEvent() 、onInterceptTouchEvent()和onTouchEvent()三个方法协助完成,如下图:
- Android事件分发流程如下:(必须熟记)
Android事件分发顺序:Activity(Window) -> ViewGroup -> View
其中:
- super:调用父类方法
- true:消费事件,即事件不继续往下传递
- false:不消费事件,事件也不继续往下传递 / 交由给父控件onTouchEvent()处理
3.2 一般的事件传递情况
一般的事件传递场景有:
- 默认情况
- 处理事件
- 拦截DOWN事件
- 拦截后续事件(MOVE、UP)
3.2.1 默认情况
- 即不对控件里的方法(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的行为是不一样的。
5.1 onTouch()和onTouchEvent()的区别
View中dispatchTouchEvent()的源码分析
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
- 这两个方法都是在View的dispatchTouchEvent中调用,但onTouch优先于onTouchEvent执行。
-
如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。
如果在onTouch方法中返回true将事件消费掉,onTouchEvent()将不会再执行。
那么首先看一下默认的触屏事件的在两个函数之间的传递流程。如下图:
如果仅仅想让MyTextView来响应触屏事件,让MyTextView的OnTouchEvent返回true,那么事件流就变成如下图,可以看到layoutview1,layoutview2已经不能进入OnTouchEvent:
另外一种情况,就是外围容器想独自处理触屏事件,那么就应该在相应的onInterceptTouchEvent函数中返回true,表示要截获触屏事件,比如layoutview1作截获处理,处理流变成如下图:
以此类推,我们可以得到各种具体的情况,整个layout的view类层次中都有机会截获,而且能看出来外围的容器view具有优先截获权。
ViewGroup里的onInterceptTouchEvent默认值是false这样才能把事件传给View里的onTouchEvent.
ViewGroup里的onTouchEvent默认值是false。
View里的onTouchEvent返回默认值是true.这样才能执行多次touch事件。(返回false,认为你没有消费,接下来其他事件都不在这里触发)
(注:可能你会觉得是否消费了有关系吗,反正我已经针对事件编写了处理代码(即你在那里写了代码处理这个事件,但是不算消费,还是会传递)?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,onTouchEvent, 如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。 )( 不消费不代表不处理 )对于ViewGroup的dispatchTouchEvent方法来说,跟Activity类似,如果此方法返回true,则该事件处理终止;如果返回false,则执行上一级的onTouchEvent方法(以Acivity中包含的第一级ViewGroup为例来说,如果这个ViewGroup又包含了子的ViewGroup,则类推为,父的ViewGroup的onTouchEvent方法). 默认的是执行super的方法,会调用到ViewGroup的onInterceptTouchEvent方法,此方法如果返回true,表示拦截此事件,则此事件不会再往下传递,并且执行ViewGroup的onTouchEvent方法;如果此方法返回false(或是执行super的方法),则事件继续往下传递,传递到View.
对于View的dispatchTouchEvent方法来说,跟Activity类似,如果此方法返回true,则该事件处理终止;如果返回false,则执行上一级也就是ViewGroup的onTouchEvent方法。默认执行super的方法的话,会调用到View的onTouchEvent方法。至此,事件由上到下的传递完成.
6.requestDisallowInterceptTouchEvent的用处,为了子View申请父类别拦截事件的方法(前提是子View可以收到Down事件)
如:在ListView
里面套了ViewPager
导致ViewPager
不能滑动的问题,通常的处理方式:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (absListView != null) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getX();
mDownY = event.getY();
//ACTION_DOWN的时候,赶紧把事件hold住
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if(Math.abs(event.getX() - mDownX)>Math.abs(event.getY()-mDownY)) {
getParent().requestDisallowInterceptTouchEvent(true);
}else {
//发现不是自己处理,还给父类
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
//其实这里是多余的
getParent().requestDisallowInterceptTouchEvent(false);
}
}
return super.onInterceptTouchEvent(event);
}
http://www.jianshu.com/p/38015afcdb58