目录
2)Activity的dispatchTouchEvent返回true
3)ViewGroup的dispatchTouchEvent返回true
4)ViewGroup的onInterceptTouchEvent返回true
5) View的dispatchTouchEvent()返回true
7)ViewGroup的onTouchEvent返回true
1.触摸事件MotionEvent:
1)主要对应3操作
ACTION_DOWN(手指触屏)
ACTION_MOVE (手指移动,ViewConfiguration.get(this).getScaledTouchSlop()获取判定为位移的阈值距离)
ACTION_UP(手指移动)
2)MotionEvent内部主要方法:
getX()获取点击位置距离当前View左边的距离
getY()获取点击位置距离当前View上边的距离
getRawX()获取点击位置距离屏幕左边的距离
getRawY()获取点击位置距离屏幕上边的距离
2.事件传递
Activity主要方法有dispatchTouchEvent()和onTouchEvent();
ViewGroup主要方法有dispatchTouchEvent()和onTouchEvent()和onInterceptTouchEvent();
View主要方法有dispatchTouchEvent()和onTouchEvent();
事件传递是由Activity/Viewgroup/View实现,自上而下传递(即默认情况的传递方向:Activity-->Viewgroup-->View)。
能够将事件消费掉的方法有dispatchTouchEvent和onTouchEvent,onInterceptTouchEvent方法主要起到拦截,并且不向下传递事件的作用。
下面将用demo演示各种场景下,3者的调用顺序,并做出总结。
1)默认情况demo:
MainActivity代码:
public class MainActivity extends Activity {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, "activity的dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "activity的onTouchEvent");
return super.onTouchEvent(event);
}
}
自定义ViewGroup代码:
public class MLinearLayout extends LinearLayout {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, "viewgroup的dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "viewgroup的onTouchEvent");
return super.onTouchEvent(event);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.e(TAG, "viewgroup的onInterceptTouchEvent");
return super.onInterceptTouchEvent(ev);
}
}
自定义View代码:
public class MTextView extends android.support.v7.widget.AppCompatTextView {
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.e(TAG, "view的dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "view的onTouchEvent");
return super.onTouchEvent(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"
android:orientation="vertical"
tools:context="com.bihucj.mcandroid.ui.MainActivity">
<com.bihucj.mcandroid.view.MLinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.bihucj.mcandroid.view.MTextView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.bihucj.mcandroid.view.MLinearLayout>
</LinearLayout>
运行后点击布局,打印日志如下:
E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: view的onTouchEvent
E/事件分发顺序: viewgroup的onTouchEvent
E/事件分发顺序: activity的onTouchEvent
可以看出Activity,ViewGroup,View都是会先走dispatchTouchEvent方法,这个就是前面所说的自上而下的分发顺序,如果在分发的时候不去消费事件,就会导致由View层自下而上的通过onTouchEvent()方法处理事件。是不是很像一个U字型。
后面demo我们的xml布局都不会变,并且不改变每一层onTouchEvent()的返回值。
2)Activity的dispatchTouchEvent返回true
打印日志如下:
E/事件分发顺序: activity的dispatchTouchEvent
可以看到dispatchTouchEvent()方法确实有消费事件的能力,也就不会向下传递事件了。
3)ViewGroup的dispatchTouchEvent返回true
打印日志如下:
E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
事件被ViewGroup层的dispatchTouchEvent方法消费了
4)ViewGroup的onInterceptTouchEvent返回true
打印日志如下:
E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: viewgroup的onTouchEvent
E/事件分发顺序: activity的onTouchEvent
可以看到onInterceptTouchEvent类似于导流的作用,U字型的走势变成了小U字型。如图:
5) View的dispatchTouchEvent()返回true
打印日志如下:
E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
可以看到事件在view的dispatchTouchEvent()方法内被消费了
6) View的onTouchEvent返回true
打印日志如下:
E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: view的onTouchEvent
可以看到,事件在view的onTouchEvent方法中被消费。如图:
7)ViewGroup的onTouchEvent返回true
打印日志如下:
E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: view的onTouchEvent
E/事件分发顺序: viewgroup的onTouchEvent
如图:
总结:老铁们,看到这了,是不是茅塞顿开,还是那2句话。
事件传递是由Activity/Viewgroup/View实现,自上而下传递(即默认情况的传递方向:Activity-->Viewgroup-->View)。能够将事件消费掉的方法有dispatchTouchEvent和onTouchEvent,onInterceptTouchEvent方法主要起到拦截,并将传递方向导流给ViewGroup的onTouchEvent。
8) 给自定义view设置点击事件
findViewById(R.id.textview).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "自定义view设置点击事件");
}
});
打印日志如下:
E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: view的onTouchEvent
E/事件分发顺序: 自定义view设置点击事件
可以看到事件同样也能被view的点击事件所消费掉。
这里的调用流程为:View(onTouchEvent)-->View(PerFormClick) -->View(Click) -->结束
9)给自定义view设置点击事件同时设置Touch监听
代码如下:
findViewById(R.id.textview).setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.e(TAG, "自定义view设置Touch事件");
return false;
}
});
findViewById(R.id.textview).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.e(TAG, "自定义view设置点击事件");
}
});
打印日志如下
E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: 自定义view设置Touch事件
E/事件分发顺序: view的onTouchEvent
E/事件分发顺序: 自定义view设置点击事件
可以看到Touch事件是在点击事件触发之前调用的。那么如果将Touch事件的返回值改为true,情况又是如何,请看打印的日志:
E/事件分发顺序: activity的dispatchTouchEvent
E/事件分发顺序: viewgroup的dispatchTouchEvent
E/事件分发顺序: viewgroup的onInterceptTouchEvent
E/事件分发顺序: view的dispatchTouchEvent
E/事件分发顺序: 自定义view设置Touch事件
可以看到,如果手动复写Touch监听,并且返回true,将不会再执行View的onTouchEvent方法。
基本上大多情形都已经列举了,谢谢大家!如有遗漏,或者错误的,帮忙指出一下,Thank You!
3.事件冲突处理的2种方式
核心思想就是:哪一层需要消费这个事件,并且不想往下传递,可以通过上面例子中的返回true来实现。所以最主要的还是将判断这一层需要消费这个事件转化为条件,满足条件后,就消费掉,所以这个条件的判断处理好,就行了。
1)外部拦截法-通过ViewGroup层去做拦截
// 分别记录上次滑动的坐标(onInterceptTouchEvent)
private int mLastXIntercept = 0;
private int mLastYIntercept = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//是否拦截,做导流处理
boolean intercepted = false;
//这些属性值,可以做为我们判断的依据
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
//down时候不能拦截
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
//这里就是所谓的条件判断,满足需要消费的条件,就拦截
if (Math.abs(deltaX) > Math.abs(deltaY)) {
intercepted = true;
} else {
intercepted = false;
}
break;
}
//记得在up动作时候不去拦截
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
2)内部拦截法-通过view层去做拦截
先重写自定义view的dispatchTouchEvent方法
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
//方法设置为true表示父容器不拦截
case MotionEvent.ACTION_DOWN: {
parent.requestDisallowInterceptTouchEvent(true);
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastX;
int deltaY = y - mLastY;
//达到父容器需要消费此事件的条件,父容器兰家
if (父容器需要此类点击事件) {
parent.requestDisallowInterceptTouchEvent(false);
}
break;
}
case MotionEvent.ACTION_UP: {
break;
}
default:
break;
}
mLastX = x;
mLastY = y;
return super.dispatchTouchEvent(event);
}
重写的子元素的dispatchTouchEvent方法,这里同时需要重写父容器的onInterceptTouchEvent方法,为什么呢?
因为ACTION_DOWN事件并不受FLAG_DISALLOW_INTERCEPT这个标记位的控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有的事件都无法传递到子元素之中,这样内部拦截法就无法起作用了。
父容器的代码中方法重写如下:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}