文章目录
View的滑动冲突问题
场景
- 场景一:外部控件和内部控件的滑动方向不一致
- 场景二:外部控件和内部控件的滑动方向一致
- 场景三:嵌套以上情况
滑动冲突的处理规则
- 场景一:当用户上下滑动的时候,需要让外部的View拦截事件,当用户左右滑动的时候,需要让内部的View拦截事件。
- 场景二:通常需要内部View响应View的滑动。
解决方式
外部拦截法
所谓外部拦截法指事件都经过父容器的拦截处理,如果父容器需要此事件就拦截,如果子View需要此事件就不拦截。
外部拦截法需要重写父容器的onInterceptTouchEvent()
方法.
在onInterceptTouchEvent()
方法中,ACTION_DOWN事件父容器必须返回false,这样事件才会传递到子元素;其次ACTION_MOVE事件根据具体需求判断是否拦截,如果父容器需要拦截就返回true,否则返回false;如果父容器在ACTION_UP时返回true,则子元素无法收到UP事件导致onClick事件失效。
伪代码
父容器
int lastInterceptX;
int lastInterceptY;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean intercepted = false;
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (/*父容器需要拦截此事件*/) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
}
lastInterceptX = x;
lastInterceptY = y;
return intercepted;
}
内部拦截法
内部拦截法指父容器不拦截事件,所有的事件都传递到子元素,如果子元素需要此事件就直接消耗,否则交给父容器处理,需要使用requestDisallowInterceptTouchEvent()
方法。
子元素的dispatchTouchEvent()
必须在ACTION_DOWN事件调用requestDisallowInterceptTouchEvent(true)
,这样才能保证子元素能收到ACTION_MOVE事件,在move事件做逻辑操作。
父容器的onInterceptTouchEvent()
方法里的ACTION_DOWN事件不能拦截,这样子元素才能收到事件。
伪代码
父容器
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
return false;
} else {
return true;
}
}
子元素
int lastX;
int lastY;
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int dx = x - lastX;
int dy = y - lastY;
if (/*父容器需要此事件*/) {
parent.requestDisallowInterceptTouchEvent(false);
} else {
parent.requestDisallowInterceptTouchEvent(true);
}
break;
case MotionEvent.ACTION_UP:
break;
}
lastX = x;
lastY = y;
return super.dispatchTouchEvent(event);
}
案例
在AndroidX中很多内置空间已经处理了一些事件冲突,但是仍然还有一些需要处理。
ScrollView嵌套ListView
public class MyScrollView extends ScrollView {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//ScrollView的滚动需要在onTouchEvent()里做一些准备,直接调用一次
onTouchEvent(ev);
return false;
} else {
return true;
}
}
}
public class MyScrollView extends ScrollView {
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//ScrollView的滚动需要在onTouchEvent()里做一些准备,直接调用一次
onTouchEvent(ev);
return false;
} else {
return true;
}
}
}
ScrollView嵌套EditText
public class MyEditText extends androidx.appcompat.widget.AppCompatEditText {
@Override
public boolean onTouchEvent(MotionEvent event) {
if (canVerticalScroll(this)) {
getParent().requestDisallowInterceptTouchEvent(true);
if (event.getAction() == MotionEvent.ACTION_UP) {
getParent().requestDisallowInterceptTouchEvent(false);
}
}
return super.onTouchEvent(event);
}
private boolean canVerticalScroll(EditText editText) {
//滚动的距离
int scrollY = editText.getScrollY();
//控件里内容的总高度
int scrollRange = editText.getLayout().getHeight();
//控件实际的高度
int scrollExtent = editText.getHeight() - editText.getCompoundPaddingTop() - editText.getCompoundPaddingBottom();
//内容的高度与控件实际高度的差值
int scrollDifference = scrollRange - scrollExtent;
if (scrollDifference == 0) {
return false;
}
return (scrollY > 0) || (scrollY < scrollDifference - 1);
}
}
NestedScrollView处理嵌套滑动
可以用NestedScrollView
替换ScrollView
进行嵌套滑动,达到子View优先效果
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView 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=".pkg4.NestedActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#5B000000"
android:gravity="center"
android:text="头部"
android:textSize="30sp" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="400dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#f00" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#0f0" />
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#00f" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<TextView
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#5B000000"
android:gravity="center"
android:text="底部"
android:textSize="30sp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>