View的事件体系三(滑动冲突处理)

本文讨论滑动冲突问题。

在界面中只要有内外两层同时可以滑动,就可能会产生滑动冲突。


一. 滑动冲突场景

  1. 外部滑动方向和内部滑动方向不一致。

    • 举个例子,ViewPager 和 Fragment配合组成的页面滑动效果。左右滑动可以切换页面, 如果页面内又是一个ListView,就会导致滑动冲突,只不过ViewPager内部已经处理了这种滑动冲突,所以我们无需担心这个问题。
    • 但是如果我们使用的不是ViewPager,而是ScrollView,那就必须手动处理冲突了。否则,内外两层将只有一层能正常滑动。
  2. 内外滑动方向一致。内外两层同时能上下滑动,或同时能左右滑动。

  3. 前面两种情况的嵌套。

二. 滑动冲突处理的一般规则

  1. 针对场景1的情况,可以根据起始点的坐标,计算滑动向量Vector(x,y):
    • 如果abs(x) > abs(y), 认定为左右滑动
    • 如果abs(x) < abs(y), 认定为上下滑动
    • x 或 y为正,则滑动方向为右或下
    • x 或 y为负,则滑动方向为左或上
  2. 针对场景2的情况,无法根据滑动向量或速度矢量来做判断。此时只能通过界面内容所呈现的业务来做判断:

    • 当业务处于某种状态时,外部响应滑动,
    • 当业务处于另一种状态时,内部响应滑动。
  3. 比场景2更复杂,也只能从业务上寻找判断的点。

三. 解决方式

  1. 外部拦截法:点击事件都必须先经过父容器的拦截处理,然后判断是否继续分发事件。

    • 重写父容器的onInterceptTouchEvent()方法,在ACTION_MOVEcase 分支,判断父容器是否需要处理当前点击事件。

      @Override
      public boolean onInterceptTouchEvent(MotionEvent event){
          boolean intercept = false;
          int x = (int) event.getX();
          int y = (int) event.getY();
          switch(event.getActon()){
              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;
              default:
                  break;
          }
          mLastXintercept = x;
          mLastYintercept = y;
          return intercept;
      }
  2. 内部拦截法:首先由子View来处理事件,如果父容器需要处理事件,才会将后续事件交由父容器处理;否则,后续事件将继续由子View来处理。

    下面是处理流程:

    1. 父容器绝不拦截down事件,根据是否需要处理事件,可能会拦截move和up。这是为了保证首先由子元素来处理事件。
    2. 子元素在分发down时,立即请求父容器禁止拦截事件:
      getParent().requestDisallowInterceptTouchEvent(true);
      这是为了确保后续事件依然由子容器处理,所以请求父容器禁止拦截事件。
    3. 子元素在分发move时,如果父容器需要处理事件,则请求父容器允许拦截后续事件(move和up)。
    4. 如果父容器不需要处理事件,那么父容器将继续保持禁止拦截事件的状态。此时所有的事件都将由子元素来处理。

    下面是参考代码:

    • 重写子元素的dispatchTouchEvent(MotionEvent event)方法:

      //子元素中重写
      @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;
              default:
                  break;
          }
          mLastX = x;
          mLastY = y;
          return super.dispatchTouchEvent(ev);
      }
    • 重写父容器的onInterceptTouchEvent,保证父容器永不拦截down,并一直拦截move和up:

      @Override
      public boolean onInterceptTouchEvent(MotionEvent event) {
          if (event.getAction() == MotionEvent.ACTION_DOWN) {
              return false;
          }
          return true;
      }

      显然,如果子元素不请求父容器禁止拦截事件:getParent().requestDisallowInterceptTouchEvent(false),父容器将一直会拦截move和up。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值