以前其实解决过类似的问题,当时是ViewPager嵌套的冲突问题,没有做记录,所以这次又费力研究半天,想想还是把代码和思路记录下来方便以后参考。

关于安卓的TouchEvent事件分发机制可以参考我之前写的blog,这里不再多说:

首先是最外层的HorizontalScrollView(后面简称HS)中的控制,当内部有一个竖向的ScrollView(后面简称VS)的时候,如果HS对上下滑动的事件做了吸收或者部分处理,就会导致HS不能滑动或者滑动卡顿的情况,事实也的确是如此,所以我们需要在HS中做如下处理:

private GestureDetector mGestureDetector; 

    private void init()
    {
        setHorizontalScrollBarEnabled(false);
        mGestureDetector = new GestureDetector(getContext(), new HScrollDetector()); 
    }

    // Return false if we're scrolling in the y direction   
    class HScrollDetector extends SimpleOnGestureListener
    {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2,
                float distanceX, float distanceY)
        {
            if (Math.abs(distanceX) > Math.abs(distanceY))
            {
                return true;
            }

            return false;
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        switch (ev.getAction())
        {
        case MotionEvent.ACTION_DOWN:
            downX = (int) ev.getX();
            break;
        }

        return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
    }

init方法会在我们覆写的HS中被调用,在这个地方我们new了一个GestureDetector,这里我们自定义了一个HScrollDetector,回调方法中捕捉了横向的滑动事件。然后看HS中的onInterceptTouchEvent方法,当为横向的滑动事件的时候,mGestureDetector.onTouchEvent(ev)返回为true,所有的逻辑和HS原有的逻辑一样,事件被拦截然后有HS自己处理;但是当滑动为纵向的时候,mGestureDetector.onTouchEvent(ev)返回为false,也就是不拦截当前事件,所有的事件就会传到下面的VS中,由VS消耗掉,不会再发生卡顿的情况。

这时候需求要求我们VS里又要嵌套一个HorizontalScrollView(下面简称IHS),这时候我们会发现上面的HS已经把横向的滑动事件吸收掉了,IHS基本拿不到事件,所以又会导致不能滑动的问题。怎么办呢,我们想到了api里的requestDisallowInterceptTouchEvent方法,可以理解为这个方法的优先级,高于父View自己的onInterceptTouchEvent,上代码:

public class InnerHScrollView extends HorizontalScrollView
{
    public InnerHScrollView(Context p_context, AttributeSet p_attrs)
    {
        super(p_context, p_attrs);
    }

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

    private boolean mCanScroll = true;

    private float mDownX;

    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        ViewParent viewParent =
                 getParent().getParent().getParent().getParent().getParent();

        if (ev.getAction() == MotionEvent.ACTION_DOWN)
        {
            mDownX = ev.getX();
        }

        if (ev.getAction() == MotionEvent.ACTION_MOVE)
        {
            int scrollx = getScrollX();
            if ((scrollx == 0 && mDownX - ev.getX() <= -10) || 
                (getChildAt(0).getMeasuredWidth() <= (scrollx + Constants.HOME_VIEW_WIDTH) && mDownX - ev.getX() >= 10))
            {
                mCanScroll = false;
            }

        }

        if (ev.getAction() == MotionEvent.ACTION_UP
                || ev.getAction() == MotionEvent.ACTION_CANCEL)
        {
            mCanScroll = true;
        }

        if (this.mCanScroll)
        {
            // 此句代码是为了通知他的父ViewPager现在进行的是本控件的操作,不要对我的操作进行干扰
            viewParent.requestDisallowInterceptTouchEvent(true);
            return super.onTouchEvent(ev);
        }
        else
        {
            viewParent.requestDisallowInterceptTouchEvent(false);
            return false;
        }
    }

}

viewParent那里写死了,要动态的话让外面把parent的层级通过数字传进来就可以了,这里就不改了,原理是一样的。当确认是正常的X轴滑动的时候,mCanScroll为true,viewParent.requestDisallowInterceptTouchEvent(true);该方法会让parent把时间传递给IHS自己处理,不做任何拦截。否则,交由上层HS自己处理。

原理不复杂,前提还是各位要充分理解安卓的touchEvent的分发机制。