滑动是Android中不可缺少的一部分,多个滑动必然会产生冲突,比如我们最常见的是ScrollView中嵌套了ListView,一般做法是计算出ListView的总高度,这样就不用去滑动ListView了。又比如一个ViewPager嵌套Fragment,Fragment里面又有ListView,这原本是有滑动冲突的,但是ViewPager内部去帮我们解决了这种冲突。那如果我们要自己解决冲突又该怎么办呢。下面有两种方式来解决


外部拦截法

外部拦截法是指在有点击事件时都要经过父容器,那么在父容器时如果需要拦截就拦截自己处理,不需要则传递给下一层进行处理,下面看个例子

首先定义一个水平滑动的HorizontalScrollViewEx,看主要代码

主要的拦截是需要重写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:
                //down事件不拦截,否则无法传给子元素
                intercepted = false;
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                    intercepted = true;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastXIntercept;
                int deltaY = y - mLastYIntercept;
                //水平滑动则拦截
                if (Math.abs(deltaX) > Math.abs(deltaY) + 5) {
                    intercepted = true;
                } else {
                    intercepted = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                //不拦截,否则子元素无法收到
                intercepted = false;
                break;
        }
        //因为当ViewGroup中的子View可能消耗了down事件,在onTouchEvent无法获取,
        // 无法对mLastX赋初值,所以在这里赋值一次
        mLastX = x;
        mLastY = y;
        mLastYIntercept = y;
        mLastXIntercept = x;
        return intercepted;
    }

在down事件不需要拦截,返回false,否则的话子view无法收到事件,将全部会由父容器处理,这不是希望的;up事件也要返回false,否则最后子view收不到

看看move事件,当水平滑动距离大于竖直距离时,代表水平滑动,返回true,由父类来进行处理,否则交由子view处理。这里move事件就是主要的拦截条件判断,如果你遇到的不是水平和竖直的条件这么简单,就可以在这里进行改变,比如,ScrollView嵌套了ListView,条件就变成,当ListView滑动到底部或顶部时,返回true,交由父类滑动处理,否则自身ListView滑动。

在onTouchEvent中主要是做的滑动切换的处理

@Override
    public boolean onTouchEvent(MotionEvent event) {
        mVelocityTracker.addMovement(event);
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if (getScrollX() < 0) {
                    scrollTo(0, 0);
                }
                scrollBy(-deltaX, 0);
                break;
            case MotionEvent.ACTION_UP:
                int scrollX = getScrollX();
                mVelocityTracker.computeCurrentVelocity(1000);
                float xVelocityTracker = mVelocityTracker.getXVelocity();
                if (Math.abs(xVelocityTracker) > 50) {//速度大于50则滑动到下一个
                    mChildIndex = xVelocityTracker > 0 ? mChildIndex - 1 : mChildIndex + 1;
                } else {
                    mChildIndex = (scrollX + mChildWith / 2) / mChildWith;
                }
                mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
                int dx = mChildIndex * mChildWith - scrollX;
                smoothScrollBy(dx, 0);
                mVelocityTracker.clear();
                break;
        }
        mLastY = y;
        mLastX = x;
        return true;
    }

在这个嵌套一个普通的ListView,这样就可以解决水平和竖直滑动冲突的问题了。

<com.example.lzy.customview.HorizontalScrollViewEx
        android:layout_width="match_parent"
        android:layout_height="200dp">

        <ListView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_blue_bright"
            android:text="2" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/holo_green_dark"
            android:text="3" />
    </com.example.lzy.customview.HorizontalScrollViewEx>

其他的部分代码如果需要可以下载源码来看


内部拦截法

内部拦截法是父容器不拦截任何事件,所有事件都传递给子view,如果需要就直接消耗掉,不需要再传给父容器处理

下面重写一个ListView,只需要重写一个dispatchTouchEvent方法就OK

public class ListViewEx extends ListView {

    private static final String TAG = "lzy";
    private int mLastX;
    private int mLastY;

    public ListViewEx(Context context) {
        super(context);
    }

    public ListViewEx(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getX();
        int y = (int) ev.getY();

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //子View的所有父ViewGroup都会跳过onInterceptTouchEvent的回调
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaX = x - mLastX;
                int deltaY = y - mLastY;
                if (Math.abs(deltaX) > Math.abs(deltaY) + 5) {//水平滑动,使得父类可以执行onInterceptTouchEvent
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
        }
        mLastX = x;
        mLastY = y;
        return super.dispatchTouchEvent(ev);
    }
}

在down事件调用getParent().requestDisallowInterceptTouchEvent(true),这句代码的意思是使这个view的父容器都会跳过onInterceptTouchEvent,在move中判断如果是水平滑动就由父容器去处理,父容器只需要把之前的onInterceptTouchEvent改为下面那样,其他不变

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            mLastX = x;
            mLastY = y;
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
                return true;
            }
            return false;
        } else {
            //如果是非down事件,说明子View并没有拦截父类的onInterceptTouchEvent
            //说明该事件交由父类处理,所以不需要再传递给子类,返回true
            return true;
        }
    }

最终实现效果就是下面那样,两个是用两种方式实现的,上面的圆圈是一个简单的自定义View练习

Android滑动值 android滑动冲突的解决方案_Android滑动值


下载地址