自定义view时经常会碰见触摸事件传递方面的问题,所以需要清楚里面的机制才能处理好相关的问题。
Android里一次完整的事件传递主要有三个阶段,分别是分发(dispatch)、拦截(intercept)和消费;
其中activity里有:dispatchTouchEvent 和 onTouchEvent;
viewGroup里有:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent;
view里有:dispatchTouchEvent 和 onTouchEvent;
其中 dispatchTouchEvent 返回 true 代表触摸事件不再进行分发,返回false或super方法表示继续进行分发;
onInterceptTouchEvent 返回true表示对触控事件进行拦截,拦截后不再向下传递同时调用onTouchEvent进行消费。返回false或super方法表表示不拦截;
onTouchEvent返回true代表事件已经消费,返回false或super表示不消费;
触摸事件的类型主要有几种:
ACTION_DOWN (触摸按压)
ACTION_MOVE (触摸移动)
ACTION_UP (触摸离开)
我们先假设应用里有activity、viewGroup和view,对应的layout文件应该是这样的:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.ziheng.tourcheventtest.MainActivity">
<com.example.ziheng.tourcheventtest.MyFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.ziheng.tourcheventtest.MyView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.example.ziheng.tourcheventtest.MyFrameLayout>
</android.support.constraint.ConstraintLayout>
我在activity、viewGroup和view分别打上log:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "dispatchTouchEvent ACTION_DOWN ");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "dispatchTouchEvent ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouchEvent ACTION_DOWN ");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
public class MyFrameLayout extends FrameLayout {
private static final String TAG = "MyFrameLayout";
public MyFrameLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "dispatchTouchEvent ACTION_DOWN ");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "dispatchTouchEvent ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onInterceptTouchEvent ACTION_DOWN ");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onInterceptTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onInterceptTouchEvent ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouchEvent ACTION_DOWN ");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
public class MyView extends View {
private static final String TAG = "MyView";
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "dispatchTouchEvent ACTION_DOWN ");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "dispatchTouchEvent ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouchEvent ACTION_DOWN ");
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
}
返回值都是默认的super,运行后触摸屏幕再松手,打印的结果是:
D/MainActivity: dispatchTouchEvent ACTION_DOWN
D/MyFrameLayout: dispatchTouchEvent ACTION_DOWN
D/MyFrameLayout: onInterceptTouchEvent ACTION_DOWN
D/MyView: dispatchTouchEvent ACTION_DOWN
D/MyView: onTouchEvent ACTION_DOWN
D/MyFrameLayout: onTouchEvent ACTION_DOWN
D/MainActivity: onTouchEvent ACTION_DOWN
D/MainActivity: dispatchTouchEvent ACTION_UP
D/MainActivity: onTouchEvent ACTION_UP
可以清晰的看到传递的顺序是 activity - viewGroup - view,其中要注意的是由于 ACTION_DOWN 事件 view 和 viewGroup 都没有消费,所以之后的事件将不再向下传递,所以上面只看到 activity 处理了 ACTION_UP 事件;
测试下把 viewGroup 的 dispatchTouchEvent 的 ACTION_DOWN 返回值设置为true:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "dispatchTouchEvent ACTION_DOWN ");
return true;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "dispatchTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "dispatchTouchEvent ACTION_UP");
break;
}
return super.dispatchTouchEvent(ev);
}
打印出来的结果为
D/MainActivity: dispatchTouchEvent ACTION_DOWN
D/MyFrameLayout: dispatchTouchEvent ACTION_DOWN
D/MainActivity: dispatchTouchEvent ACTION_UP
D/MyFrameLayout: dispatchTouchEvent ACTION_UP
D/MyFrameLayout: onTouchEvent ACTION_UP
D/MainActivity: onTouchEvent ACTION_UP
ACTION_DOWN 的返回值设置为 true 之后,ACTION_DOWN 事件不再向下分发;
设置onInterceptTouchEvent的ACTION_DOWN事件返回值为true
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onInterceptTouchEvent ACTION_DOWN ");
return true;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onInterceptTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onInterceptTouchEvent ACTION_UP");
break;
}
return super.onInterceptTouchEvent(ev);
}
打印的结果为:
D/MainActivity: dispatchTouchEvent ACTION_DOWN
D/MyFrameLayout: dispatchTouchEvent ACTION_DOWN
D/MyFrameLayout: onInterceptTouchEvent ACTION_DOWN
D/MyFrameLayout: onTouchEvent ACTION_DOWN
D/MainActivity: onTouchEvent ACTION_DOWN
D/MainActivity: dispatchTouchEvent ACTION_UP
D/MainActivity: onTouchEvent ACTION_UP
onInterceptTouchEvent 返回 true 后 ACTION_DOWN 事件被拦截并执行 viewGroup 里的 onTouchEvent 方法;
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onTouchEvent ACTION_DOWN ");
return true;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onTouchEvent ACTION_MOVE");
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "onTouchEvent ACTION_UP");
break;
}
return super.onTouchEvent(event);
}
把 view 的 onTouchEvent 中的 ACTION_DOWN 事件返回 true, 看下情况会怎么样:
D/MainActivity: dispatchTouchEvent ACTION_DOWN
D/MyFrameLayout: dispatchTouchEvent ACTION_DOWN
D/MyFrameLayout: onInterceptTouchEvent ACTION_DOWN
D/MyView: dispatchTouchEvent ACTION_DOWN
D/MyView: onTouchEvent ACTION_DOWN
D/MainActivity: dispatchTouchEvent ACTION_UP
D/MyFrameLayout: dispatchTouchEvent ACTION_UP
D/MyFrameLayout: onInterceptTouchEvent ACTION_UP
D/MyView: dispatchTouchEvent ACTION_UP
D/MyView: onTouchEvent ACTION_UP
D/MainActivity: onTouchEvent ACTION_UP
由于 view 消费了 ACTION_DOWN 事件,所以 ACTION_UP 事件会继续传递到 view 进行处理;
下面两幅图可以清晰的对比这两种情况触摸事件的传递不同:
view 不消费事件
view 消费事件