1.描述
Android中的事件在表现形式上有很多。如onTouch(触摸)、onClick(点击)和onLongClick(长按)等等。在具体微观上的表现形势有action_down、action_move和action_up等等。
无论哪种事件表现类型,首先都是基于事件的传递模型。其实Android中的事件传递有点类似于JS中事件传递模型。都是基于先捕获然后冒泡的形式。
在捕获阶段,事件先由外部的View接收,然后传递给其内层的View,依次传递到更够接收此事件的最小View单元,完成事件捕获过程。
在冒泡阶段,事件则从事件源的最小View单元开始,依次向外冒泡,将事件对层传递。事件的捕获和冒泡是整个事件的传递流程,但是在实际的传递过程中,Android中则表现的相对复杂。
主要表现在可以控制每层事件是否继续传递(由事件分发和事件拦截协同进行),以及事件的具体消费(由事件消响应进行,但需要注意的是,事件分发自身也具有事件消费能力)。
Android中不同的控件所具有的事件分发、拦截和响应稍有不同,主要表现在Activity本身不具有事件拦截,不是ViewGroup的最小view单元不具有事件分发和事件拦截(因为它没有自己的子View)。
所有的UI控件:Button,TextView等等都是继承View。
所有的布局控件:RelativeLayout,LinearLayout等等继承ViewGroup。
所有容器控件:ListView,ScrollView等等继承于ViewGroup。
所以,我们的事件操作主要就是发生在View和ViewGroup之间,那么View和ViewGroup中主要有哪些方法来对这些事件进行响应呢。
记住如下3个方法,我们通过查看View和ViewGroup的源码可以看到:
View
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
ViewGroup
public boolean dispatchTouchEvent(MotionEvent event)
public boolean onTouchEvent(MotionEvent event)
public boolean onInterceptTouchEvent(MotionEvent event)
三个方法说明:
public boolean dispatchTouchEvent(MotionEvent ev); //用来分派event
public boolean onInterceptTouchEvent(MotionEvent ev); //用来拦截event
public boolean onTouchEvent(MotionEvent ev); //用来处理event
也就是说在View和ViewGroup中都存在dispatchTouchEvent和onTouchEvent方法,但是在ViewGroup中还有一个onInterceptTouchEvent方法,这些方法的返回值全部都是boolean型,为什么是boolean型呢,看看本文的标题,“事件传递”,传递的过程就是一个接一个,那到了某一个点后是否要继续往下传递呢?你发现了吗,“是否”二字就决定了这些方法应该用boolean来作为返回值。没错,这些方法都返回true或者是false。在Android中,所有的事件都是从开始经过传递到完成事件的消费,这些方法的返回值就决定了某一事件是否是继续往下传,还是被拦截了,或是被消费了。
2.代码说明
2.1.View级别
此demo描述自定义了一个button 重写了dispatchTouchEvent和onTouchEvent方法 在activity页面中触摸,点击此button 并且activity也重写了dispatchTouchEvent和onTouchEvent方法
自定义button
public class MyButtons extends android.support.v7.widget.AppCompatButton {
public MyButtons(Context context) {
super(context);
}
public MyButtons(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyButtons(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int action=event.getAction();
switch(action){
case MotionEvent.ACTION_DOWN://按下
Log.d("TAG", "Button----<dispatchTouchEvent>----按下!!!");
break;
case MotionEvent.ACTION_MOVE://移动
Log.d("TAG", "Button----<dispatchTouchEvent>----移动!!!");
break;
case MotionEvent.ACTION_UP://抬起
Log.d("TAG", "Button----<dispatchTouchEvent>----抬起!!!");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action=event.getAction();
switch(action){
case MotionEvent.ACTION_DOWN://按下
Log.d("TAG", "Button----<onTouchEvent>----按下!!!");
break;
case MotionEvent.ACTION_MOVE://移动
Log.d("TAG", "Button----<onTouchEvent>----移动!!!");
break;
case MotionEvent.ACTION_UP://抬起
Log.d("TAG", "Button----<onTouchEvent>----抬起!!!");
break;
}
return super.onTouchEvent(event);
}
}
Activity类
public class EventsActivity extends AppCompatActivity {
private MyButtons button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_events);
button=(MyButtons) findViewById(R.id.mybuttons);
//button触摸事件
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
int action=arg1.getAction();
switch(action){
case MotionEvent.ACTION_DOWN://按下
Log.d("TAG", "Button----<onTouch>----按下!!!");
break;
case MotionEvent.ACTION_MOVE://移动
Log.d("TAG", "Button----<onTouch>----移动!!!");
break;
case MotionEvent.ACTION_UP://抬起
Log.d("TAG", "Button----<onTouch>----抬起!!!");
break;
}
return false;
}
});
//button点击事件
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
Log.d("TAG", "Button----<onClick>----点击!!!");
}
});
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action=ev.getAction();
switch(action){
case MotionEvent.ACTION_DOWN://按下
Log.d("TAG", "Activity----<dispatchTouchEvent>----按下!!!");
break;
case MotionEvent.ACTION_MOVE://移动
Log.d("TAG", "Activity----<dispatchTouchEvent>----移动!!!");
break;
case MotionEvent.ACTION_UP://抬起
Log.d("TAG", "Activity----<dispatchTouchEvent>----抬起!!!");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action=event.getAction();
switch(action){
case MotionEvent.ACTION_DOWN://按下
Log.d("TAG", "Activity----<onTouchEvent>----按下!!!");
break;
case MotionEvent.ACTION_MOVE://移动
Log.d("TAG", "Activity----<onTouchEvent>----移动!!!");
break;
case MotionEvent.ACTION_UP://抬起
Log.d("TAG", "Activity----<onTouchEvent>----抬起!!!");
break;
}
return super.onTouchEvent(event);
}
}
*****************************************************
效果
触摸button时结果(为了避免太多输出 没有移动)
由此可见(无论“按下” “移动” “抬起”)执行顺序为
Activity dispatchTouchEvent方法
——>Button dispatchTouchEvent方法
——>Button OnTouch方法
——>Button onTouchEvent方法
——>Button OnClick方法。
即顺序从Activity——>View Acctivity的onTouchEvent方法并未执行。
*****************************************************
其他不变将Button的dispatchTouchEvent方法返回true
效果
触摸button时结果(为了避免太多输出 没有移动)
即此时从Activity传到Button的dispatchTouchEvent方法,由于此方法返回true,Button的其他方法不再执行。即不在往下传递。
*****************************************************
其他不变将Button的onTouch方法返回true
Button的onTouch方法返回true后,Button的onTouchEvent方法和OnClick方法不执行。
*****************************************************
其他不变将Button的onTouchEvent方法返回true
效果
触摸button时结果(为了避免太多输出 没有移动)
Button的onTouchEvent方法返回true时OnClick方法不执行。
*****************************************************
此时其他不变将Activity的dispatchTouchEvent的返回值变成true
效果
触摸button时结果(为了避免太多输出 没有移动)
即此时如论Button的其他方法返回值如何,只要设置了Activity的dispatchTouchEvent的返回值变成true,Button的所有方法都不会执行。
*****************************************************
此时其他不变将Activity的onTouchEvent的返回值变成true
效果
触摸button时结果(为了避免太多输出 没有移动)
和返回super.onTouchEvent(event)时没有区别。
源码
Activity.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
从源码中可以看到,dispatchTouchEvent方法只处理了ACTION_DOWN事件,前面提到过,所有的事件都是以按下为起点的,所以,Android认为当ACTION_DOWN事件没有执行时,后面的事件都是没有意义的,所以这里首先判断ACTION_DOWN事件。如果事件成立,则调用了onUserInteraction方法,该方法可以在Activity中被重写,在事件被分发前会调用该方法。该方法的返回值是void型,不会对事件传递结果造成影响,接着会判断getWindow().superDispatchTouchEvent(ev)的执行结果,看看它的源码:
/**
* Used by custom windows, such as Dialog, to pass the touch screen event
* further down the view hierarchy. Application developers should
* not need to implement or call this.
*
*/
public abstract boolean superDispatchTouchEvent(MotionEvent event);
通过源码注释我们可以了解到这是个抽象方法,用于自定义的Window,例如自定义Dialog传递触屏事件,并且提到开发者不需要去实现或调用该方法,系统会完成,如果我们在MainActivity中将dispatchTouchEvent方法的返回值设为true,那么这里的执行结果就为true,从而不会返回执行onTouchEvent(ev),如果这里返回false,那么最终会返回执行onTouchEvent方法,由此可知,接下来要调用的就是onTouchEvent方法了。
通过日志输出信息可以看到,ACTION_DOWN事件从Activity被分发到了MyButton,接着执行了onTouch和onTouchEvent方法,为什么先执行onTouch方法呢?
当我们的手从屏幕抬起时,会发生ACTION_UP事件。从之前输出的日志信心中可以看到,ACTION_UP事件同样从Activity开始到myButton进行分发和处理,最后,由于我们注册了onClick事件,当onTouchEvent执行完毕后,就调用了onClick事件,那么onClick是在哪里被调用的呢?
我们可以知道在Android中,事件是通过层级传递的,一次事件传递对应一个完整的层级关系,例如上面分析的ACTION_DOWN事件从Activity传递到MyButton,ACTION_UP事件也同样。结合源码分析各个事件处理的方法,也可以明确看到事件的处理流程。
到目前为止,Android中的事件拦截机制就分析完了。但这里我们只讨论了单布局结构下单控件的情况,如果是嵌套布局,那情况又是怎样的呢?接下来我们就在嵌套布局的情况下对Android的事件传递机制进行进一步的探究和分析。
2.2.ViewGroup级别
新建一个类myLayout继承于LinearLayout,同样重写dispatchTouchEvent和onTouchEvent方法,另外,还需要重写onInterceptTouchEvent方法,在文章开头介绍过,这个方法只有在ViewGroup中才存在,作用是控制是否需要拦截事件。这里不要和dispatchTouchEvent弄混淆了,后者是控制对事件的分发,并且后者要先执行。那么,事件是先传递到View呢,还是先传递到ViewGroup的?通过下面的分析我们可以得出结论。
自定义Linearlayout
public class MyLinearlayout extends LinearLayout {
public MyLinearlayout(Context context) {
super(context);
}
public MyLinearlayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLinearlayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int action=event.getAction();
switch(action){
case MotionEvent.ACTION_DOWN://按下
Log.d("TAG", "Linearlayout----<dispatchTouchEvent>----按下!!!");
break;
case MotionEvent.ACTION_MOVE://移动
Log.d("TAG", "Linearlayout----<dispatchTouchEvent>----移动!!!");
break;
case MotionEvent.ACTION_UP://抬起
Log.d("TAG", "Linearlayout----<dispatchTouchEvent>----抬起!!!");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action=ev.getAction();
switch(action){
case MotionEvent.ACTION_DOWN://按下
Log.d("TAG", "Linearlayout----<onInterceptTouchEvent>----按下!!!");
break;
case MotionEvent.ACTION_MOVE://移动
Log.d("TAG", "Linearlayout----<onInterceptTouchEvent>----移动!!!");
break;
case MotionEvent.ACTION_UP://抬起
Log.d("TAG", "Linearlayout----<onInterceptTouchEvent>----抬起!!!");
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action=event.getAction();
switch(action){
case MotionEvent.ACTION_DOWN://按下
Log.d("EventsActivity", "Linearlayout----<onTouchEvent>----按下!!!");
break;
case MotionEvent.ACTION_MOVE://移动
Log.d("EventsActivity", "Linearlayout----<onTouchEvent>----移动!!!");
break;
case MotionEvent.ACTION_UP://抬起
Log.d("EventsActivity", "Linearlayout----<onTouchEvent>----抬起!!!");
break;
}
return super.onTouchEvent(event);
}
}
java代码
layout=(MyLinearlayout) findViewById(R.id.mylinearlayout);
//layout触摸事件
layout.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View arg0, MotionEvent arg1) {
int action=arg1.getAction();
switch(action){
case MotionEvent.ACTION_DOWN://按下
Log.d("TAG", "layout----<onTouch>----按下!!!");
break;
case MotionEvent.ACTION_MOVE://移动
Log.d("TAG", "layout----<onTouch>----移动!!!");
break;
case MotionEvent.ACTION_UP://抬起
Log.d("TAG", "layout----<onTouch>----抬起!!!");
break;
}
return false;
}
});
//layout点击事件
layout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
Log.d("TAG", "layout----<onClick>----点击!!!");
}
});
由于上面讲述了Activity到View的事件传递 此处之操作ViewGroup到View的传递
*****************************************************
效果
触摸button时结果(为了避免太多输出 没有移动)
由此可见(无论“按下” “移动” “抬起”)执行顺序为
Activity dispatchTouchEvent方法
——>LinearLayout dispatchTouchEvent方法
——>LinearLayout onInterceptTouchEvent方法
——>Button dispatchTouchEvent方法
——>Button OnTouch方法
——>Button onTouchEvent方法
——>Button OnClick方法。
LinearLayout的点击事件并未执行。ViewGroup的onTouchEvent也没执行。
*****************************************************
其他不变将LinearLayout的dispatchTouchEvent方法返回true
效果
触摸button时结果(为了避免太多输出 没有移动)
LinearLayout的dispatchTouchEvent方法返回true,事件不往下传递。
*****************************************************
其他不变将LinearLayout的onInterceptTouchEvent方法返回true
效果
触摸button时结果(为了避免太多输出 没有移动)
LinearLayout的onInterceptTouchEvent方法返回true,即ViewGroup将事件拦截下来不传给自己的子View,而是自己消费。ViewGroup的onTouchEvent也没执行。
如果ViewGroup的onInterceptTouchEvent方法返回true,即将事件传递给自己的子View,如第一次。
在紧挨着上面的基础上将LinearLayout的OnTouch返回true
效果
触摸button时结果(为了避免太多输出 没有移动)
和View一样OnTouch返回true,ViewGroup的点击事件不执行。
以上我们对Android事件传递机制进行了分析,期间结合系统源码对事件传递过程中的处理情况进行了探究。通过单布局情况和嵌套布局情况下的事件传递和处理进行了分析,现总结如下:
Android中事件传递按照从上到下进行层级传递,事件处理从Activity开始到ViewGroup再到View。
事件传递方法包括dispatchTouchEvent、onTouchEvent、onInterceptTouchEvent,其中前两个是View和ViewGroup都有的,最后一个是只有ViewGroup才有的方法。这三个方法的作用分别是负责事件分发、事件处理、事件拦截。
onTouch事件要先于onClick事件执行,onTouch在事件分发方法dispatchTouchEvent中调用,而onClick在事件处理方法onTouchEvent中被调用,onTouchEvent要后于dispatchTouchEvent方法的调用。