Android 滑动冲突解决

 当有滑动效果的多个View嵌套使用的时候,就有可能导致滑动冲突问题。

    其本质其实是,滑动事件分发出了问题,即我们希望一个滑动事件在某种状态下交由A View来处理,却交给了B View来处理。解决问题的方法,当然就是根据具体场景的特点,通过滑动轨迹或者业务状态,判断出各种情况下的滑动操作与目标View的对应关系,然后通过View的onInterceptTouchEvent()等方法将滑动事件在各个状态下分发给对应的目标View。

常见场景有3种,但解决思路总结起来都是上面那一套:

1.内部View和外部View滑动方向不一致:通过滑动轨迹的方向就可以判断出当前应该分发给谁。

2.内部View和外部View滑动方向一致:要结合业务逻辑状态判断出当前应该分发给谁。

3.多层View嵌套,既有内部外部滑动方向不一致的情况,也有滑动方向一致的情况:将以上两种方法结合起来。
 

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嵌套RecyclerView

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);
    }
}

如对此有疑问,请联系qq1164688204。

推荐Android开源项目

项目功能介绍:RxJava2 和Retrofit2 项目,使用Kotlin+RxJava2+Retrofit2+MVP架构+组件化/Kotlin+Retrofit2+协程+MVVM架构+组件化,添加自动管理token 功能,添加RxJava2 生命周期管理,集成极光推送、阿里云Oss对象存储和高德地图定位功能。

项目地址:https://gitee.com/urasaki/RxJava2AndRetrofit2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值