Android开发艺术探索笔记(12)- View的滑动冲突

这个问题的确我也遇到过,copy别人自定义View的demo,发现导入eclipse或者android stdio不能正常工作,然后根据网上一些解决经验,并不在意原来这都一种套路来的,都是遇到问题,一上来就百度,解决了就不了了之,下次遇到了,再百度,并不了解其原理。书中作者提供了通用的解决办法和解决的思路,受益匪浅!!

View的滑动冲突有3种场景:

(1)父容器和子View滑动方式不相同的冲突。如类似ViewPager水平滑动的View,子View的Layout有ListView,ViewPager是水平滑动个,而ListView是竖直滑动的,滑动的方式不相同。当然ViewPager已经解决冲突问题。

(2)父容器和子View滑动方式相同的冲突。如ScrollView包含ListView,两者都是监听竖直滑动,方向相同。

(3)嵌套以上2种的冲突。这种情况最复杂。

Android开发艺术探索笔记(12)- View的滑动冲突_第1张图片

第(1)种场景的滑动冲突是最容易解决的,作者提供两种解决办法的思路:外部拦截法和内部拦截法。

外部拦截法就是在父容器onInterceptTouchEvent方法内判断是否符合自己的滑动方式,再决定拦不拦截。但是父容器拦截ACTION_DOWN事件时必须返回false,因为如果拦截ACTION_DOWN的话,接下来的事件都会交给父容器处理,子View则没有机会收到事件,更谈不上处理事件。在拦截ACTION_UP事件也必须返回false,因为父容器拦截ACTION_UP事件没有意义,一旦返回true(拦截),子View就不能响应ACTION_UP事件,进一步导致子View的Click事件不能响应。其实这种方法就是在ACTION_MOVE事件做判断,是否拦截ACTION_MOVE的事件,毕竟是滑动事件,最有用也就是ACTION_MOVE事件了。具体利用外部拦截法解决场景(1)代码如下。

// 自定义父容器
private int lastX;
private int lastY;
private int moveX;
private Scroller scroller;
// 初始化
private void inti() {
    scroller = new Scroller(getContext());
}
// 重写父容器的onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean isIntercept = false;
    int x = (int) event.getX();
    int y = (int) event.getY();
    switch(event.getAction) {
        case MotionEvent.ACTION_DOWN : 
            isIntercept = false;
            // 如果动画没有完成,继续响应事件,并停止当前的动画
            if (!scroller.isFinished()) {
                scroller.abortAnimation();
                isIntercept = true;
            }
            break;
        case MotionEvent.ACTION_MOVE :
            // 例如父容器需要水平滑动
            int deltaX = x - lastX;
            int deltaY = y - lastY;
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                isIntercept = true;
            } else {
                isIntercept = false;
            }
            break;
        case MotionEvent.ACTION_UP :
            isIntercept = false;
            break;
    }
    lastX = x;
    lastY = y;
    return isIntercept;
}
// 重写父容器的onTouchEvent,如果onInterceptTouchEvent返回true,就会执行这个方法
public boolean onTouchEvent(MotionEvent event) {
    int x = (int) event.getX();
    switch(event.getAction) {
        case MotionEvent.ACTION_DOWN : 
            // 我觉得作者写这几行重复的代码多余了,因为上面已经执行了,不知道对不对??
            if (!scroller.isFinished()) {
                scroller.abortAnimation();
            }
            moveX = 0;
            break;
        case MotionEvent.ACTION_MOVE :
            int deltaX = x - lastX;
            moveX += deltaX;
            scrollBy(-deltaX, 0);
            break;
        case MotionEvent.ACTION_UP :
            /** 
             * 如果moveX是正数,证明手指向右滑动,如果超过screenWidth的一半,scroller将要向右移动(screenWidth-x)的距离,显示前一个页面,否则scroller将要向左移动x的距离,让原来的页面回到原点。
             **/
            int slideX = -x; 
            if (moveX > screenWidth / 2) {
                slideX = screenWidth - x;
            }
            scroller.startScroll(getScrollX(), 0, slideX, 0, 500);
            invalidate();
            break;
    }
}
// 重写computeScroll方法配合Scroller实现滑动
public void computeScroll() {
    if(scroller.computeScrollOffset()) {
        scrollTo(scroll.getCurrentX(), scroll.getCurrentY());
        postInvalidate();
    }
}

内部拦截稍微复杂点,就是父容器不做拦截,直接传递给子View处理事件。如果符合子View的滑动方式,就消耗这个事件,否则交回给父容器处理。主要利用了子View设置父容器的一个标志位FLAG_DISALLOW_INTERCEPT,是否让父容器拦截事件。子View拦截ACTION_DOWN事件时,设置让父容器不能拦截事件。在ACTION_MOVE判断是否符合自己的滑动规则,如果不符合,允许父容器拦截事件。大概意思就是这样。具体思想如下代码。

// 重写子View的dispatchTouchEvent
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 - lastX;
            int deltaY = y - lastY;
            if (Math.abs(deltaX) > Math.abs(deltaY)) {
                parent.requestDisallowInterceptTouchEvent(false);
            }
            break;
        case MotionEvent.ACTION_UP :
            break;
    }
    lastX = x;
    lastY = y;
    return super.dispatchTouchEvent(event);
}
// 还要重写父容器的onInterceptTouchEvent方法,除了ACTION_DOWN不要拦截,其他都拦截
public boolean onInterceptTouchEvent(MotionEvent event) {
    if (event.getAction == MotionEvent.ACTION_DOWN) {
        // 这样子View就可以拦截ACTION_DOWN事件
        return false;
    } else {
        // true表示拦截,因为子View在拦截ACTION_DOWN已经设置父容器不能拦截事件,所以拦截了没有效果,只有当子View设置父容器可以拦截,这个true再次起作用
        return true;
    }
}

以上只是第(1)种场景,博客写代码,超级不方便的说!!

不过经过第(1)中场景,其实想法已经出来的了。解决其他两种也是这两种思想,只不过判断的依据不一样罢了。第一种根据的是手指滑动的距离,而其他2种就得看具体业务而定。写累了,其他两种有兴趣的看书。

代码有什么不对的地方,烦请指出,因为是纯手打的。

你可能感兴趣的:(Android,读书笔记)