简书上有一篇写的蛮不错的...Android View的事件分发及拦截机制分析,不过我们也自己打印下看看流程吧! 然后琢磨下哪些场景需要处理这个事件分发,需要解决这个事件冲突。小白印象中,ViewPaper与横向滑动的RecycleView/ListView事件冲突的问题比较经常看到!(这个小白会看sdk文档里面的一些方法介绍).
小白也打印下看看妮?
package me.heyclock.hl.customcopy;
import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
public class CustomConstraintLayout extends ConstraintLayout {
public CustomConstraintLayout(Context context) {
this(context, null);
}
public CustomConstraintLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
return super.onInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomConstraintLayout onTouchEvent" );
return super.onTouchEvent(event);
}
}
package me.heyclock.hl.customcopy;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;
@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
public CustomTextView(Context context) {
this(context, null);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0, 0);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr,0);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomTextView dispatchTouchEvent");
return super.dispatchTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomTextView onTouchEvent" );
return super.onTouchEvent(event);
}
}
setContentView(R.layout.custom_viewgroup_event);
<?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff00ee">
<me.heyclock.hl.customcopy.CustomTextView
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#ffaa00ee"
android:text="大大的中间"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<me.heyclock.hl.customcopy.CustomTextView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#ffddaaee"
android:text="小小的中间"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</me.heyclock.hl.customcopy.CustomConstraintLayout>
当点击大大的中间以及小小的中间,结果都是类似:
10-30 03:47:28.676 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
CustomConstraintLayout onTouchEvent
10-30 03:47:43.932 8046-8046/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
CustomConstraintLayout onTouchEvent
如码友分析的一样。事件的传递顺序是上册控件依次传递到下层控件,直到View. 而事件的处理顺序,则由底层的View依次返给上级,直到最顶层进行处理。
事件传递的返回值:true,拦截,不继续;false,不拦截,继续流程。
事件处理的返回值:true,处理了,不用审核了;false,给上级处理。
初始情况下,返回值都是false。
为true就表示处理了,不用在往上传递了。So,我们就可以选择某个传递的环节消费掉该事件,或者拦截掉事件传递,让其不往下层传递;比如在CustomConstraintLayout 的onInterceptTouchEvent中返回true进行拦截,这样中间的空间就不会响应了。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" );
//return super.onInterceptTouchEvent(ev);
return true;
}
10-30 04:04:30.397 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomConstraintLayout onTouchEvent
10-30 04:04:32.916 8337-8337/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomConstraintLayout onTouchEvent
再比如仅仅把CustomTextView的
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomTextView onTouchEvent" );
//return super.onTouchEvent(event);
return true;
}
事件传递继续到底,onTouch不再往上到顶
10-30 04:11:57.038 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
10-30 04:11:57.132 8840-8840/me.heyclock.hl.customcopy D/event: CustomConstraintLayout dispatchTouchEvent
CustomConstraintLayout onInterceptTouchEvent
CustomTextView dispatchTouchEvent
CustomTextView onTouchEvent
目前小白也只是知道这样的一个效果。现在有两个问题要考虑下:
1. 运行两遍?
2. 有什么场景我们可以考虑实践一下?
第一个问题,因为down和up都会走.....所以会进行两次事件传递。也就是down先传递到底层,然后up再次传递到底层(如果你进行了滑动,还会有move事件传递...)。到底层后我们在View里面return true - 消费掉了这个事件,所以上层不再收到相关事件处理。
第二个问题,有什么场景需要做特殊处理呢?
比如我们要实现这样的处理(子View可以进行左右滑动, 同时, 当在子View上面进行上下滑动时,依然是上层ViewGroup进行上下滑动.)
思路一下:
1. 子View控件onTouchEvent返回true,保证响应处理相关touch事件(down,move,up)
2. 父ViewGroup在onInterceptTouchEvent中进行处理和拦截move事件(由于左右滑动时,子View需要做响应,所以只能拦截上下滑动的情况 - 这种情况返回true,表示父ViewGroup自己处理move事件,不再交给子View)
So,我们简单定义一下ViewGroup和View
package me.heyclock.hl.customcopy;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;
@SuppressLint("AppCompatCustomView")
public class CustomTextView extends TextView {
public CustomTextView(Context context) {
this(context, null);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0, 0);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public CustomTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomTextView dispatchTouchEvent" + ev.getAction());
return super.dispatchTouchEvent(ev);
}
private float x1, x2;
private float y1, y2;
private float swing = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomTextView onTouchEvent" + event.getAction());
//return super.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
break;
case MotionEvent.ACTION_MOVE: ///< 上下滑动已经被上层拦截了,所以这里肯定就是左右滑动了
x2 = event.getX();
///< 5作为阀值就可以了,可以根据效果调整
if(y1 - y2 > 5) { ///< 上
} else if(y2 - y1 > 5) { ///< 下
} else if(x1 - x2 > 5) { ///< 左
swing = x1 - x2;
///< 采用系统View类的滚动方法
scrollBy((int) swing, 0);
invalidate();
} else if(x2 - x1 > 5) { ///< 右
swing = -(x2 - x1);
///< 采用系统View类的滚动方法
scrollBy((int) swing, 0);
invalidate();
}
x1 = x2;
y1 = y2;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
}
package me.heyclock.hl.customcopy;
import android.content.Context;
import android.support.constraint.ConstraintLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
public class CustomConstraintLayout extends ConstraintLayout {
public CustomConstraintLayout(Context context) {
this(context, null);
}
public CustomConstraintLayout(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CustomConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout dispatchTouchEvent"+ ev.getAction());
return super.dispatchTouchEvent(ev);
}
private float x1, x2;
private float y1, y2;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
Log.d("event", "CustomConstraintLayout onInterceptTouchEvent" + ev.getAction());
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
x1 = ev.getX();
y1 = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
x2 = ev.getX();
y2 = ev.getY();
///< 5作为阀值就可以了,可以根据效果调整
if(y1 - y2 > 15) { ///< 上
return true;
} else if(y2 - y1 > 15) { ///< 下
return true;
} else if(x1 - x2 > 15) { ///< 左
} else if(x2 - x1 > 15) { ///< 右
}
x1 = x2;
y1 = y2;
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onInterceptTouchEvent(ev);
}
/**
* 滚动相关(上下滑动)
*/
private float wx2;
private float wy2;
private float swing = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
wx2 = event.getX();
wy2 = event.getY();
///< 5作为阀值就可以了,可以根据效果调整
if(y1 - wy2 > 5) { ///< 上
swing = y1 - wy2;
///< 采用系统View类的滚动方法
scrollBy(0, (int) swing);
invalidate();
Log.e("test", "上滑动");
} else if(wy2 - y1 > 5) { ///< 下
swing = -(wy2 - y1);
///< 采用系统View类的滚动方法
scrollBy(0, (int) swing);
invalidate();
Log.e("test", "下滑动");
} else if(x1 - wx2 > 50) { ///< 左
} else if(wx2 - x1 > 50) { ///< 右
}
x1 = wx2;
y1 = wy2;
break;
case MotionEvent.ACTION_UP:
break;
}
return super.onTouchEvent(event);
}
}
布局 custom_viewgroup_event.xml
<?xml version="1.0" encoding="utf-8"?>
<me.heyclock.hl.customcopy.CustomConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffff00ee">
<me.heyclock.hl.customcopy.CustomTextView
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#ffaa00ee"
android:text="大大的中间"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</me.heyclock.hl.customcopy.CustomConstraintLayout>
效果...

还有问题,就是上下滑动子View的外部还不行?
这个也很简单了,有时候一直盯着子View,所以忘记了onTouch事件处理顺序了!只需要把父ViewGroup的onTouchEvent返回true不就可以接收后续的move,up事件了么....
/**
* 滚动相关(上下滑动)
*/
private float wx2;
private float wy2;
private float swing = 0;
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("event", "CustomConstraintLayout onTouchEvent" + event.getAction());
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
x1 = event.getX();
y1 = event.getY();
break;
case MotionEvent.ACTION_MOVE:
wx2 = event.getX();
wy2 = event.getY();
///< 5作为阀值就可以了,可以根据效果调整
if(y1 - wy2 > 5) { ///< 上
swing = y1 - wy2;
///< 采用系统View类的滚动方法
scrollBy(0, (int) swing);
invalidate();
Log.e("test", "上滑动");
} else if(wy2 - y1 > 5) { ///< 下
swing = -(wy2 - y1);
///< 采用系统View类的滚动方法
scrollBy(0, (int) swing);
invalidate();
Log.e("test", "下滑动");
} else if(x1 - wx2 > 50) { ///< 左
} else if(wx2 - x1 > 50) { ///< 右
}
x1 = wx2;
y1 = wy2;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;//super.onTouchEvent(event);
}
这样我们就大体实现了这个目标...

这个的理解我们先这样,回去再清醒清醒。下一篇打算去看sdk的官方文档ViewGroup | Android Developers 以及一些个常用的方法,比如类似这种getParent().requestDisallowInterceptTouchEvent(false);
下班之前妮看两篇文章吧...
事件分发:onTouchEvent返回false一定不执行ACTION_MOVE吗?
(转)浅谈onInterceptTouchEvent、onTouchEvent与onTouch - oZuiJiaoWeiYang的专栏 - CSDN博客