View的滑动冲突学习笔记

我们比较常见的滑动冲突一般有三种,这也是面试里面可能会问到的内容。第一种是外部滑动方向和内部滑动方向不一致;第二种是外部滑动方向和内部滑动方向一致;第三种比较复杂上面两种情况的嵌套;

先说说第一种情况,一般出现的场景是ViewPager和Fragment配合使用所组成的页面滑动效果,在这种效果可以通过左右滑动来切换Fragment的页面,但是如果Fragment页面里面是ListView集合。那么这就会产生滑动冲突,但是Viewpager内部给我们处理了这种滑动冲突,因此采用Viewpager 我们无需关注这种问题,如果我们采用的不是Viewpager 而是ScrollView呢?那么就必须手动处理滑动冲突了。否则造成的后果就是内外两层只有一层能滑动,这是因为两者之间的滑动事件有冲突。除了这种典型情况外,还存在其他情况,比如外部上下滑动、内部左右滑动等,但是它们属于同一类滑动冲突。

它的处理规则是:当用户左右滑动时,需要让外部的View拦截点击事件,当用户上下滑动时,需要让内部View拦截点击事件。这个时候我们就可以根据它们的特征来解决滑动冲突,具体来说是:根据滑动是水平滑动还是竖直滑动来判断到底由谁来拦截事件,如图3-5所示,根据滑动过程中两个点之间的坐标就可以得出到底是水平滑动还是竖直滑动。如何根据坐标来得到滑动的方向呢?这个很简单,有很多可以参考,比如可以依据滑动路径和水平方向所形成的夹角,也可以依据水平方向和竖直方向上的距离差来判断,某些特殊时候还可以依据水平和竖直方向的速度差来做判断。这里我们可以通过水平和竖直方向的距离差来判断,比如竖直方向滑动的距离大就判断为竖直滑动,否则判断为水平滑动。根据这个规则就可以进行下一步的解决方法制定了

这种情况有两种解决方式,第一个是外部拦截方法,第二个是内部拦截方法,也就是说,我们可以通过父元素拦截,或者从子元素拦截,从子元素拦截比较繁琐一点,我们先说第一种外部拦截方法从父元素拦截。

所谓外部拦截法是指点击事情都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要此事件就不拦截,这样就可以解决滑动冲突的问题,这种方法比较符合点击事件的分发机制。外部拦截法需要重写onInterceptTouchEvent方法。在内部做出相应的拦截即可:

    @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;
        }
        mLastXIntercept = x;
        mLastYIntercept = x;
        return intercepted;
    }

这里需要注意我们DOWN 动作 不可以拦截,如果拦截了,那么后续的事件都会被拦截,这里引用到了我们上一篇解析源码的一部分知识点,就是如果父View的事件没有传递到子View中那么mFirstTouchTarget 就是null,所以判断条件无法通过,也就不会把下面的事件分发下去,剩下的Move和Up无法传递到子View了。而且Up也同样不可以拦截,如果拦截了子View接受不到Up事件,那么就无法触发onClick事件,这就是外部拦截法的介绍,接下来我们说以下内部拦截方法。

内部拦截法就是父View不拦截任何事件,所有的事件全部交给子View,如果子View需要就消耗掉,不需要就交给父View进行处理。这种方法需要配合requestDisallowInterceptTouchEvent这个方法才能正常执行

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

                break;
        }
        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(event);
    }

这里我必须要说以下以免某些初学者看半天也转不过来,也避免我以后忘记,在DOWN事件中我们调用了一次父容器的requestDisallowInterceptTouchEvent(true)方法,并且传递了true过去!在父容器中disallowIntercept 就会被设置成true,同时也就禁用了事件拦截方法的调用。所有事件都传给子元素。在下面MOVE事件中我们写了一个判断又打开了对事件的拦截 传入了false值。这时候我们的父容器又可以获得事件拦截的条件了。但是我们不可以拦截DOWN事件!

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

第二种情况这种情况就稍微复杂一些,当内外两层都在同一个方向可以滑动的时候,显然存在逻辑问题。因为当手指开始滑动的时候,系统无法知道用户到底是想让哪一层滑动,所以当手指滑动的时候就会出现问题,要么只有一层能滑动,要么就是内外两层都滑动得很卡顿。在实际的开发中,这种场景主要是指内外两层同时能上下滑动或者内外两层同时能左右滑动。

第三种情况是第一种情况和第二种情况两种情况的嵌套,因此第三种情况的滑动冲突看起来就更加复杂了。比如在许多应用中会有这么一个效果:内层有一个第一种情况中的滑动效果,然后外层又有一个第二种情况的滑动效果。具体说就是,外部有一个SlideMenu效果,然后内部有一个ViewPager,ViewPager的每一个页面中又是一个ListView。虽然说场景3的滑动冲突看起来更复杂,但是它是几个单一的滑动冲突的叠加,因此只需要分别处理内层和中层、中层和外层之间的滑动冲突即可,而具体的处理方法其实是和第一种情况、第二种情况相同的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值