文章目录

  • View的滑动冲突问题
  • 场景
  • 滑动冲突的处理规则
  • 解决方式
  • 外部拦截法
  • 内部拦截法
  • 案例
  • ScrollView嵌套ListView
  • ScrollView嵌套EditText
  • NestedScrollView处理嵌套滑动
  • 代码下载


View的滑动冲突问题

场景

android 冲突 短按 长按 安卓事件冲突_android 冲突 短按 长按

  • 场景一:外部控件和内部控件的滑动方向不一致
  • 场景二:外部控件和内部控件的滑动方向一致
  • 场景三:嵌套以上情况

滑动冲突的处理规则

  • 场景一:当用户上下滑动的时候,需要让外部的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>