美文网首页
Android View | View 的滑动冲突

Android View | View 的滑动冲突

作者: 南子李 | 来源:发表于2020-08-04 21:02 被阅读0次

滑动冲突在实际项目中十分常见,解决滑动冲突的核心是结合事件的分发机制,了解事件的分发可参考这里

常见的滑动冲突场景

  • 场景1 :内外层滑动方向不一致
场景1.png

外层布局可以在水平方向左右滑动,内层布局可以在垂直方向上下滑动。这种情况类似于外层布局 View1 采用的是 TabLayout 和 ViewPager 实现 ViewPager 的切换,ViewPager可以左右滑动。在ViewPager 内部即内层布局 View 2 是 RecyclerView 可以实现上下滑动,每一个 ViewPager 都有一个 RecyclerView。当屏幕有滑动事件时,如果是左右滑动则 ViewPager 拦截滑动事件,如果是上下滑动则 RecyclerView 拦截滑动事件。

如何判断滑动方向?

根据滑动过程中两个点的坐标就可以判断滑动的方向。

  1. 夹角法:根据滑动方向和水平方向的夹角判断,比如夹角小于 45° 判断为左右滑动,夹角大于 45° 判断为上下滑动。
  2. 距离法:根据水平方向和竖直方向的距离差判断,比如水平方向的距离大于竖直方向的距离,判断为左右滑动;竖直方向的距离大于水平方向的距离,判断为上下滑动。距离差值便于计算,推荐使用此方法。
  3. 速度法:根据水平方向和竖直方向的速度判断。
  • 场景2:内外层滑动方向一致
场景2.png

这种场景很好理解,嵌套布局中外层布局 View1 和内层布局 View2 有相同的滑动方向。这种情况下当手指滑动时,应当判断是滑动 View1 还是滑动 View2。
实际应用中,通常会有滑动的状态来判断由谁来处理滑动。比如当处于状态1时可以滑动 View1,当处于状态2时可以滑动 View2,这里的状态通常是指界面布局中 View 在滑动时的显示状态。

  • 场景3:多种冲突结合
场景3.png

滑动冲突的解决方式

从前面的讲解中了解到,要处理滑动的冲突就要在 View 的 onInterceptTouchEvent() 中去拦截事件,事件的拦截要运用到 View 事件分发的知识,可以参考[...]。

  • 外部拦截法

外部拦截法就是先由父容器去判断滑动事件是否要处理,如果要处理该滑动事件就拦截,不处理则把当前滑动事件分发给它的内部元素,这样就可以解决滑动冲突的问题。符合事件分发的顺序,使用外部拦截法需要重写父容器的 onInterceptTouchEvent() 方法,在方法中根据情况去判断是否拦截滑动事件,这种方法的伪代码如下:

    private int mLastInterceptX;
    private int mLastInterceptY;

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        boolean intercepted = false;
        int x = (int) event.getX();
        int y = (int) event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_UP:
                intercepted = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if (父容器需要拦截事件的条件) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            default:
                break;
        }
        mLastInterceptX = x;
        mLastInterceptY = y;

        return intercepted;
    }

在 onInterceptTouchEvent() 中,首先是 ACTION_DOWN 事件,该事件一定不能拦截,一旦拦截了 ACTION_DOWN 事件同一事件序列的 ACTION_MOVE、ACTION_UP 都会被父容器拦截并直接处理,子元素将不能接收到任何事件。然后是 ACTION_MOVE,根据父容器拦截事件的条件来判断是否拦截,最后是 ACTION_UP,这里必须要返回 false,因为 ACTION_UP 本身没有什么意义。

  • 内部拦截法

内部拦截法是指父容器不再拦截任何事件,所有事件都传给子元素,如果子元素需要此事件则直接处理掉,如果不需要则交由给父容器处理,在重写子元素的 dispatchTouchEvent() 方法时需要配合使用 requestDisallowInterceptTouchEvent() 方法才能正常工作,内部拦截法的伪代码如下:

  1. 重写子元素的 dispatchTouchEvent()
    private ViewGroup parent;
    private int mLastX;
    private int mLastY;

    @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 deltaX = x - mLastX;
                int deltaY = y - mLastY;

                if (子元素要处理事件的条件) {
                    parent.requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        mLastX = x;
        mLastY = y;

        return super.dispatchTouchEvent(event);
    }
  1. 重写父容器的 onInterceptTouchEvent()
    @Override
    public boolean onInterceptHoverEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            return false;
        } else {
            return true;
        }
    }

父容器的 onInterceptTouchEvent() 中不拦截除了 ACTION_DOWN 以外的其他事件,因为一旦拦截了 ACTION_DOWN 事件,那么其后续的 ACTION_MOVE 和 ACTION_UP 将不会再传给子元素,这就违背了使用内部拦截法时父容器将所有事件都交给子元素处理的原则。

相关文章

网友评论

      本文标题:Android View | View 的滑动冲突

      本文链接:https://www.haomeiwen.com/subject/syvirktx.html