参考链接:View的事件分发机制和滑动冲突解决方案
Android事件冲突处理方案无非两种:内部拦截法和外部拦截法
外部拦截法伪代码:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//外部拦截法
boolean intercept = false;
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercept = false;
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要事件) {
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
default:
break;
}
return intercept;
}
内部拦截法伪代码:
主要通过requestDisallowInterceptTouchEvent(),该方法可以让父容器的事件拦截失效,那么如果父View的拦截失效,则子View即可获得事件
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要事件){
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
由于父容器的down事件不受requestDisallowInterceptTouchEvent()影响(因为在down事件拦截前会执行重置操作),而down事件如果被父容器拦截,则后续事件都由父容器处理,因此需要在父容器的事件拦截中实现如下代码才能完成内部拦截法的全过程
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//内部拦截法
if (ev.getAction() == MotionEvent.ACTION_DOWN){
return false;
}
return true;
}
接下来我们通过一个例子来演示事件冲突的处理,该例子中就是父容器ScrollView中嵌套了ListView,同方向的滑动冲突,布局文件如下:
<?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=".MainActivity">
<ScrollView
android:id="@+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@color/purple_200"
android:text="textView1"
android:textColor="@color/white" />
<TextView
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@color/purple_700"
android:text="textView2"
android:textColor="@color/white" />
<TextView
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@color/purple_200"
android:text="textView1"
android:textColor="@color/white" />
<TextView
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@color/purple_700"
android:text="textView2"
android:textColor="@color/white" />
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="@color/purple_200"
android:text="textView1"
android:textColor="@color/white" />
<TextView
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@color/purple_700"
android:text="textView2"
android:textColor="@color/white" />
<TextView
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@color/purple_200"
android:text="textView1"
android:textColor="@color/white" />
<TextView
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="@color/purple_700"
android:text="textView2"
android:textColor="@color/white" />
</LinearLayout>
</ScrollView>
</android.support.constraint.ConstraintLayout>
在不处理滑动冲突时,效果如下,ScrollView可以滑动,ListView不能滑动
下面需要处理事件冲突,因为外层的ScrollView和内层的ListView都是竖向滑动,因此我们要根据业务逻辑来处理事件的分发,业务逻辑为:当向下滑动,ListView已经滑到顶时,接下来滑动事件给到ScrollView;当向上滑动,ListView已经滑到底时,接下来滑动事件给到父容器;其他时候事件交给ListView自己处理
外部拦截处理代码:
int pointY = 0;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//外部拦截法
boolean intercept = false;
int nowY = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
pointY = nowY;
intercept = super.onInterceptTouchEvent(ev);
break;
case MotionEvent.ACTION_MOVE:
if (mListView.getFirstVisiblePosition() == 0 && nowY > pointY) {
//下滑到顶 继续下滑
intercept = true;
} else if (mListView.getLastVisiblePosition() == mListView.getCount() - 1 && nowY < pointY) {
//上滑到底 继续上滑
intercept = true;
} else {
intercept = false;
}
break;
case MotionEvent.ACTION_UP:
intercept = false;
break;
default:
break;
}
return intercept;
}
public void setListView(ListView listView) {
this.mListView = listView;
}
内部拦截处理代码:
int pointY;
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int nowY = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
pointY = nowY;
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
if (getFirstVisiblePosition() == 0 && nowY>pointY){
//在顶 继续下滑
getParent().requestDisallowInterceptTouchEvent(false);
}else if (getLastVisiblePosition() == getCount()-1 && nowY<pointY){
//在底 继续上滑
getParent().requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.dispatchTouchEvent(ev);
}
父容器的onInterceptTouchEvent也需要处理
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//内部拦截法
if (ev.getAction() == MotionEvent.ACTION_DOWN){
super.onInterceptTouchEvent(ev);
return false;
}
return true;
}
处理后的效果如下:
项目代码: