以前其实解决过类似的问题,当时是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的分发机制。