安卓View—滑动冲突


文章目录

  • 安卓View—滑动冲突
  • 一、前言
  • 二、常见的滑动冲突场景
  • 场景1
  • 场景2
  • 场景3
  • 三、滑动冲突的处理规则
  • 场景1处理规则:
  • 场景2处理规则:
  • 场景3处理规则:
  • 四、滑动冲突的解决方式
  • 1.外部拦截法
  • 2.内部拦截法


一、前言

滑动冲突在开发过程中遇到的情况还挺多的,我以前开发 码助 等项目的时候遇到过,当然在使用抖音的时候也遇到过(当场给学姐提交BUG,结果修复的还挺快),我以前解决滑动冲突都只是在网上找方法解决问题,没有去了解过这方面的问题。

二、常见的滑动冲突场景

常见的滑动冲突场景可以分为以下三种情况:

  • 场景1:外部滑动方向和内部滑动方向不同
  • 场景2:外部滑动方向和内部滑动方向一致
  • 场景3:以上两种情况的嵌套

android 拖动绘制 android滑动选择_ide

场景1

主要是将ViewPager与Fragment相结合使用组成的页面滑动效果,在这种效果中可以通过左右滑动切换画面,而每个页面内部为ListView或者RecyclerView,在这种情况下往往会发生滑动冲突,但是ViewPager内部处理了此滑动冲突,在使用ViewPager的时候无需关心此问题,但是在使用其他滑动父布局如:ScrollView的时候就得关注并解决了。

场景2

这种情况在当内部与外部滑动方向在同一方向能滑动的时候,就会存在逻辑问题,当用户手指开始滑动的时候,系统无法知道用户用户到底是想让那一层滑动,所以当手指滑动的时候就会出现问题,要么只有一层滑动或者滑动卡顿。

场景3

场景3是场景1与场景2的嵌套,所以场景3的滑动冲突看起来更加复杂, 但它只不过是几个单一的滑动冲突的叠加,因此,只要分别处理外层和中层,中层和内层的滑动冲突即可。

三、滑动冲突的处理规则

场景1处理规则:

当用户左右滑动的时候需要让外部的View拦截点击事件,当用户上下滑动的时候需要让内部的View拦截点击事件。即通过判断滑动是水平滑动还是竖直滑动来判断谁来拦截事件。

怎么判断水平滑动还是竖直滑动:

  • 根据滑动路径和水平方向所形成的夹角
  • 根据水平方向和竖直方向的距离差
  • 根据水平与竖直方向的速度差来判断

场景2处理规则:

场景2比较特殊,他无法根据滑动的角度,距离差和速度差来判断,但他一般都能在业务上找到突破点。比如,业务规定,当处于某种状态时是,外部View响应,当处于另一种状态时,内部View响应。根据这个规则对滑动进行相应的处理。

场景3处理规则:

对于场景3来说,它的滑动规则更复杂,和场景2一样,它也无法根据滑动的角度,距离差和速度差来判断,同样只能通过业务上找到突破点。

四、滑动冲突的解决方式

1.外部拦截法

思路:
外部拦截是指点击事件都要经过父容器的拦截处理,如果父容器需要次事件则拦截,如果不需要则不拦截。这种方法比较符合点击事件的分发机制。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false;
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        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;
        }
        mLastXIntercept = x;
        mLastYIntercept = y;
        return intercept;
    }

解释
上面就是外部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改当前点击事件这个条件即可。

onInterceptTouchEvent方法中,首先是ACTION_DOWN这个事件,父容器必须返回false,即不拦截ACTION_DOWN,这是因为一旦父容器拦截ACTION _DOWM,那么后续的ACTION_MOVEACTION_UP事件都会直接交给父容器处理。其次ACTION_MOVE事件,这个事件可以根据需求是否拦截。最后是ACTION_UP事件,这里必须返回false,因为ACTION_UP事件本身没有太多意义。

2.内部拦截法

思路

内部拦截法是指父容器不拦截任何事件,所有事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android的事件分发机制不一样,需要配合requestDisallowInterceptTouEvent方法才能正常工作,使用起来较外部拦截法稍复杂。

伪代码:

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if (父容器需要此类点击事件) {
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(ev);
    }

重写父元素的onInterceptTouchEvent方法

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if(action == MotionEvent.ACTION_DOWN){
            return false;
        }else {
            return true;
        }
    }

解释
上面就是内部拦截法的典型逻辑,针对不同的滑动冲突,只需要修改当前点击事件这个条件即可。

在内部拦截法中,父元素要默认拦截除ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisallowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。、

父元素不能拦截ACTION_DOWN事件原因是,ACTION_DOWN不受FLAG_DISALLOW_DOWN这个标志位控制,所以一旦父容器拦截ACTION_DOWN事件,那么所有事件都无法传递给子元素。