Android滑动冲突解决

##.总结

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

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

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

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

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

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

##.具体解决方法和示例

    考虑典型场景:一个ViewGroup内部嵌套了一个非ViewGroup的View,两个组件都能进行滑动操作。其它多层嵌套的情况,都可以通过该场景的思路来处理。

1.外部拦截法,只需要调整外部ViewGroup,示例代码如下:

思路:修改拦截逻辑,ACTION_DOWN要放过,否则所有事件都无法传递到子View;ACTION_UP要放过,否则子View的所有点击事件都会失效。针对ACTION_MOVE做需要的判断,针对需要的情况,做拦截由自己处理,否则就传递给子View处理。

//记录最近一次事件发生的位置

private int mLastX;

private int mLastY;

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

    boolean intercept = super.onInterceptTouchEvent(ev);

    //获取当前事件发生的位置

    int x = (int)ev.getX();

    int y = (int)ev.getY();

    switch (ev.getAction()){

        case MotionEvent.ACTION_DOWN:

            intercept = false;

            break;

        case MotionEvent.ACTION_MOVE:

            //此处要根据自己的需要来做判断是否拦截

            //这里只是一个简单的示例:如果向左上方、左下方滑动,就拦截

            intercept = (x < mLastX);

            break;

        case MotionEvent.ACTION_UP:

            break;

        default:

            break;

    }

    //更新位置记录

    mLastX = x;

    mLastY = y;

    return intercept;

}

2.内部拦截法,外部ViewGroup、内部View都需要调整,示例代码如下:

思路:修改内部View的分发逻辑,当ACTION_DOWN传递过来时关闭掉父View的拦截功能;针对ACTION_MOVE做需要的判断,针对需要的情况,继续由自己处理,否则就关闭父View的拦截功能,由父View拦截处理。

内部View:

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

    int x = (int)ev.getX();

    int y = (int)ev.getY();

    switch (ev.getAction()){

        case MotionEvent.ACTION_DOWN:

            //关闭父View的拦截功能

            getParent().requestDisallowInterceptTouchEvent(true);

            break;

        case MotionEvent.ACTION_MOVE:

            //如果向右上方、右下方滑动,就由自身来处理

            //否则,开启父View的拦截功能,由父View来处理

            if(x < mLastX){

                getParent().requestDisallowInterceptTouchEvent(false);

            }

            break;

        case MotionEvent.ACTION_UP:

            break;

        default:

            break;

    }

    mLastX = x;

    mLastY = y;

    return super.dispatchTouchEvent(ev);

}



外部ViewGroup:

@Override

public boolean onInterceptTouchEvent(MotionEvent ev) {

    int action = ev.getAction();

    if(action == MotionEvent.ACTION_DOWN){

        return false;

    } else if(action == MotionEvent.ACTION_MOVE){

        //如果内部View设置让拦截功能重新开启了,说明内部View不需要捕获,应该由外部View来拦截处理

        //所以只要是滑动事件,而且拦截生效了,就应该拦截

        return true;

    } else {

        return super.onInterceptTouchEvent(ev);

    }

}

##.通用性解决思路

    推荐使用外部拦截法。原因有二:

    一方面,内部嵌套法适合View树上作为叶子节点的View使用。因为ViewGroup默认是不做拦截的,作为ViewGroup即使使用了内部拦截法,也需要额外调整代码做拦截处理,否则最终事件会继续分发给子View来处理。

    另一方面,内部拦截法需要修改内部、外部两处View的逻辑,较麻烦一些。

    对于多层嵌套的滑动冲突情况,只需要对发生冲突的每层ViewGroup使用外部拦截法,理论上就可以解决所有滑动冲突。

主要参考:《Android开发艺术探索》第3章;

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124594366
今日推荐