一.简述
android上所有的事件操作都是基于用户对屏幕的触摸与滑动进行分解,进而对用户不同的操作进行监听;如:点击事件、双击事件、长按事件等等。
一次完整的事件传递主要包含三个阶段,分别是事件的分发(Dispatch)、拦截(Intercept)、消费(Consume)。
关键词
- ACTION_DOWN:用户手指按下屏幕:
- ACTIOJN_MOVE:用户手指在屏幕上滑动,滑动的距离大于阀值,即产生滑动事件;
- ACTiON_UP:用户手指离开屏幕:
- 分发(Dispatch):事件分发,即dispatchTouchEvent();在android中所有的触摸事件都有由dispatchTouchEvent()进行分发处理的。返回true事件被消费,返回super.dispatchTouchEvent(),会触发拦截机制;
- 拦截(Intercept): 事件拦截,即onInterceptTouchEvent();该方法,只在ViewGroup及其子类中存在,view和Activity 中不存在。实现逻辑,返回true,被拦截,返回false或super.onInterceptTouchEvent(),向下传递;
- 消费(Consume):事件消费,即onTouchEvent();返回true,事件被消费掉(处理),不会向上传递给父视图。返回false,事件不会被处理,向上传递给父视图,进行事件分发;
视图与事件的对应
视图 | 分发 | 拦截 | 消费 |
---|---|---|---|
Activity | dispatchTouchEvent | -- | onTouchEvent |
ViewGroup | dispatchTouchEvent | onInterceptTouchEvent | onTouchEvent |
View | dispatchTouchEvent | -- | onTouchEvent |
二.View的事件传递
代码
- Activity:EventActivity
public class EventActivity extends AppCompatActivity {
private static final String TAG = EventActivity.class.getSimpleName();
EventTextView etvEvent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event);
etvEvent = (EventTextView) findViewById(R.id.etv_event);
etvEvent.setOnClickListener(onClickListener);
etvEvent.setOnTouchListener(onTouchListener);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent ACTION_CANCEL");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent ACTION_CANCEL");
break;
}
return super.onTouchEvent(event);
}
private View.OnClickListener onClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "onClickListener ONCLICK");
}
};
private View.OnTouchListener onTouchListener = new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchListener ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchListener ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchListener ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchListener ACTION_CANCEL");
break;
}
return false;
}
};
}
- 自定义View:EventTextView
public class EventTextView extends AppCompatTextView {
private static final String TAG = EventTextView.class.getSimpleName();
public EventTextView(Context context) {
super(context);
}
public EventTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public EventTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent ACTION_CANCEL");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent ACTION_CANCEL");
break;
}
return super.onTouchEvent(event);
}
}
- 布局文件:activity_event.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="study.zxh.com.androidstudydemp.event.EventActivity">
<study.zxh.com.androidstudydemp.event.EventTextView
android:id="@+id/etv_event"
android:layout_width="match_parent"
android:layout_height="50dp" android:gravity="center_vertical"
android:text="android事件传递机制测试--view"
android:textSize="16dp" />
</LinearLayout>
代码概述
相关代码优先级:
Activity > ViewGroup > View
ACTION_DOWN > ACTION_MOVE > ACTION_UP
dispatchTouchEvent > OnTouchListener代码结构很简单,分别对Activity和View的dispatchTouchEvent以及onTouchEvent进行的日志记录;同时,对常用的事件OnClickListener以及OnTouchListener进行了记录,对于得到的结果,可以划分为以下几种:
1.事件被activity 的dispatchTouchEvent消费:
结论
- 事件消费后,不会再向下传递,也不会触发activity的onTouchEvent();
2.事件被View 的OnTouchListener消费:
结论
- 事件消费后,不会触发activty的onTouchEvent()方法;
- 事件消费后,会触发view绑定的OnTouchListener监听;
- 事件的传递,每次都是由Activity的dispatchTouchEvent开始的;
- 事件的传递,OnTouchListener的优先级高于onTouchEvent的优先级;
- 事件的监听依赖与onTouchEvent方法;
3.事件被View的dispatchTouchEvent消费:
结论
- 事件被消费,不会触发任何的onTouchEvent()方法;
- 事件被消费,不会触发任何的监听事件;
4.事件被View的onTouchEvent消费:
结论
- 所有事件都会单向传递到View的onTouchEvent方法,但是不会再向上传递;
- 事件传递的顺序是:
dispatchTouchEvent(Activity)-->dispatchTouchEvent(View)-->onTouchListener(View)-->onTouchEvent(View) - 没有调用到onClickListener。
5.事件被Activity的onClickListener消费:
结论
- onClick事件发生在View的onTouchEvent方法之后,消费掉就不会再向上传递;
- 事件传递的顺序是:
dispatchTouchEvent(Activity)-->dispatchTouchEvent(View)-->onTouchListener(View)-->onTouchEvent(View)-->onClickListener
6.事件被Activity的onTouchEvent消费:
结论
- 会触发Activity以及View的所有相关方法;
- 事件触发的流程是以Activity的dispatchTouchEvent开始,以activity的onTouchEvent结束
二.View的事件传递
代码
- 自定义ViewGroup:EventLinearLayout
public class EventLinearLayout extends LinearLayout {
private static final String TAG = "EventLinearLayout";
public EventLinearLayout(Context context) {
super(context);
}
public EventLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public EventLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "dispatchTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "dispatchTouchEvent ACTION_CANCEL");
break;
}
return super.dispatchTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e(TAG, "onTouchEvent ACTION_DOWN");
break;
case MotionEvent.ACTION_MOVE:
Log.e(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.e(TAG, "onTouchEvent ACTION_UP");
break;
case MotionEvent.ACTION_CANCEL:
Log.e(TAG, "onTouchEvent ACTION_CANCEL");
break;
}
return super.onTouchEvent(event);
}
}
- 修改布局文件:activity_event.xml
<?xml version="1.0" encoding="utf-8"?>
<study.zxh.com.androidstudydemp.event.EventLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="study.zxh.com.androidstudydemp.event.EventActivity">
<study.zxh.com.androidstudydemp.event.EventTextView
android:id="@+id/etv_event"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:text="android事件传递机制测试--view"
android:textSize="16dp" />
</study.zxh.com.androidstudydemp.event.EventLinearLayout>
1.事件被ViewGroup的dispatchTouchEvent 消费
结论
- 事件被ViewGroup的dispatchTouchEvent消费,没有触发任何onTouchEvent();
2.ViewGroup的dispatchTouchEvent 返回false
结论
- 事件没有被ViewGroup消费;
- 事件不会向下传递;
- 没有调用ViewGroup的interceptTouchEvent(拦截器);
- 事件回传到activity的onTouchEvent后事件结束;
3.ViewGroup的dispatchTouchEvent 返回super.dispatchTouchEvent()
结论
- 事件会正常向下传递,若没有被消费,则会回传给activity的onTouchEvent处理;
4. 事件被ViewGroup的interceptTouchEvent 拦截
结论
- 事件被拦截之后,会直接调用ViewGroup的onTouchEvent进行处理;
- 事件被拦截,就不会再向下传递。
5. ViewGroup的interceptTouchEvent 返回 false
结论
- 当返回false时,事件正常向下传递,不会有任何影响;
6. ViewGroup的interceptTouchEvent 返回 super.onInterceptTouchEvent()
结论
- 返回super.onInterceptTouchEvent(),显示的结结果,与返回false相同;
问题:若是多层ViewGroup嵌套,又是什么结果呢?
7. 事件被ViewGroup的onTouchEvent 消费
结论
- 事件会先触发View的onTouchEvent,然后再出发ViewGroup的TouchEvent;
8. ViewGroup的onTouchEvent 返回false或super.onTouchEvent()
结论
- 两种情况效果相同
结论
- 事件被ViewGroup拦截,没有触发任何onTouchEvent();
三. 分析返回false与super的区别
(在ViewGroup嵌套ViewGroup情况下)
代码
- 修改布局文件:activity_event.xml
<?xml version="1.0" encoding="utf-8"?>
<study.zxh.com.androidstudydemp.event.EventLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="study.zxh.com.androidstudydemp.event.EventActivity">
<study.zxh.com.androidstudydemp.event.EventLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="study.zxh.com.androidstudydemp.event.EventActivity">
<study.zxh.com.androidstudydemp.event.EventTextView
android:id="@+id/etv_event"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:text="android事件传递机制测试--view"
android:textSize="16dp" />
</study.zxh.com.androidstudydemp.event.EventLinearLayout>
</study.zxh.com.androidstudydemp.event.EventLinearLayout>
1.dispatchTouchEvent
activty的disptchTouchEvent返回false
activty的disptchTouchEvent返回super
ViewGroup的disptchTouchEvent返回false
ViewGroup的disptchTouchEvent返回super
View的disptchTouchEvent返回false
View的disptchTouchEvent返回super
2.interceptTouchEvent
interceptTouchEvent返回false
interceptTouchEvent返回super
3.onTouchEvent
onTouchEvent返回super
onTouchEvent返回false
总结
1.dispatchTouchEvent返回false,事件不会继续向下传递,但是会向上调用,调用上一级的onTouchEvent,进行处理;
2.dispatchTouchEvent返回super,事件会向下传递,调用相应等级的dispatchTouchEvent或interceptTouchEvnt进行继续处理;
3.interceptTouchEvent与onTouchEvent返回false或super没有区别;
备注:dispatchTouchEvent返回false和super,没有区别,因为它们没有需要向下传递的途径
四. 事件分发机制流程
详解
- 事件由activity的dispatchTouchEvent开始,事件被消费即结束;
- 事件没有被消费,若返回false,直接调用上一级的onTouchEvent进行消费;
- 若返回super,会执行同级别下的相关方法,即如下两种途径:
1). 若是ViewGroup,先执行interceptTouchEvent方法,返回true即拦截,运行同级别onTouchEvent方法进行消费;返回false或super,事件向下传递;
2). 若不是ViewGroup,事件向下传递,进入view层,再次进行事件分发,若被消费,事件结束,若没有被消费,事件会由View的onTouchEvent依次传回到Activity的onTouchEvent方法最终结束;
完整的事件传递流程
dispatchTouchEvent(Activity)--> dispatchTouchEvent(ViewGroup)--> interceptTouchEvent(ViewGroup) -->dispatchTouchEvent(View)--> onTouchEvent(View) --> onTouchEvent(ViewGroup)--> onTouchEvent(Activity)