修复RecyclerView嵌套滚动问题

2016-09-14  本文已影响0人  void_Zhao

原文:http://blog.chengyunfeng.com/?p=1017&utm_source=tuicool&utm_medium=referral

在 Android 应用中,大部分情况下都会使用一个垂直滚动的 View 来显示内容(比如 ListView、RecyclerView 等)。但是有时候你还希望垂直滚动的View 里面的内容可以水平滚动。如果直接在垂直滚动的 View 里面使用水平滚动的 View,则滚动操作并不是很流畅。

比如下图中的示例:

为什么会出现这个问题呢?

上图中的布局为一个 RecyclerView 使用的是垂直滚动的 LinearLayoutManager 布局管理器,而里面每个 Item 为另外一个 RecyclerView 使用的是水平滚动的 LinearLayoutManager。而在Android系统的事件分发中,即使最上层的 View 只能垂直滚动,当用户水平拖动的时候,最上层的 View 依然会拦截点击事件。下面是 RecyclerView.java 中 onInterceptTouchEvent 的相关代码:

Java

```

@Override

publicbooleanonInterceptTouchEvent(MotionEvente){

...

switch(action){

case MotionEvent.ACTION_DOWN:

...

case MotionEvent.ACTION_MOVE:{

...

if(mScrollState!=SCROLL_STATE_DRAGGING){

booleanstartScroll=false;

if(canScrollHorizontally&&Math.abs(dx)>mTouchSlop){

...

startScroll=true;

}

if(canScrollVertically&&Math.abs(dy)>mTouchSlop){

...

startScroll=true;

}

if(startScroll){

setScrollState(SCROLL_STATE_DRAGGING);

}

}

}break;

...

}

returnmScrollState==SCROLL_STATE_DRAGGING;

}

```

注意上面的 if 判断:

Java

if(canScrollVertically&&Math.abs(dy)>mTouchSlop){...}

RecyclerView 并没有判断用户拖动的角度, 只是用来判断拖动的距离是否大于滚动的最小尺寸。 如果是一个只能垂直滚动的 View,这样实现是没有问题的。如果我们在里面再放一个 水平滚动的 RecyclerView ,则就出现问题了。

可以通过如下的方式来修复该问题:

Java

if(canScrollVertically&&Math.abs(dy)>mTouchSlop&&(canScrollHorizontally||Math.abs(dy)>Math.abs(dx))){...}

下面是一个完整的实现BetterRecyclerView.java

Java

```

public class BetterRecyclerView extends RecyclerView{

private static final int INVALID_POINTER=-1;

private int mScrollPointerId=INVALID_POINTER;

private int mInitialTouchX,mInitialTouchY;

private int mTouchSlop;

public BetterRecyclerView(Contextcontext){

this(context,null);

}

publicBetterRecyclerView(Contextcontext,@NullableAttributeSetattrs){

this(context,attrs,0);

}

public BetterRecyclerView(Contextcontext,@NullableAttributeSetattrs,intdefStyle){

super(context,attrs,defStyle);

final ViewConfigurationvc=ViewConfiguration.get(getContext());

mTouchSlop=vc.getScaledTouchSlop();

}

@Override

public void setScrollingTouchSlop(intslopConstant){

super.setScrollingTouchSlop(slopConstant);

final ViewConfigurationvc=ViewConfiguration.get(getContext());

switch(slopConstant){

case TOUCH_SLOP_DEFAULT:

mTouchSlop=vc.getScaledTouchSlop();

break;

case TOUCH_SLOP_PAGING:

mTouchSlop=ViewConfigurationCompat.getScaledPagingTouchSlop(vc);

break;

default:

break;

}

}

@Override

public boolean onInterceptTouchEvent(MotionEvente){

final intaction=MotionEventCompat.getActionMasked(e);

final intactionIndex=MotionEventCompat.getActionIndex(e);

switch(action){

case MotionEvent.ACTION_DOWN:

mScrollPointerId=MotionEventCompat.getPointerId(e,0);

mInitialTouchX=(int)(e.getX()+0.5f);

mInitialTouchY=(int)(e.getY()+0.5f);

return super.onInterceptTouchEvent(e);

case MotionEventCompat.ACTION_POINTER_DOWN:

mScrollPointerId=MotionEventCompat.getPointerId(e,actionIndex);

mInitialTouchX=(int)(MotionEventCompat.getX(e,actionIndex)+0.5f);

mInitialTouchY=(int)(MotionEventCompat.getY(e,actionIndex)+0.5f);

return super.onInterceptTouchEvent(e);

case MotionEvent.ACTION_MOVE:{

finalintindex=MotionEventCompat.findPointerIndex(e,mScrollPointerId);

if(index<0){

return false;

}

final int x=(int)(MotionEventCompat.getX(e,index)+0.5f);

final int y=(int)(MotionEventCompat.getY(e,index)+0.5f);

if(getScrollState()!=SCROLL_STATE_DRAGGING){

final int dx=x-mInitialTouchX;

final int dy=y-mInitialTouchY;

final boolean canScrollHorizontally=getLayoutManager().canScrollHorizontally();

final boolean canScrollVertically=getLayoutManager().canScrollVertically();

boolean startScroll=false;

if(canScrollHorizontally&&Math.abs(dx)>mTouchSlop&&(Math.abs(dx)>=Math.abs(dy)||canScrollVertically)){

startScroll=true;

}

if(canScrollVertically&&Math.abs(dy)>mTouchSlop&&(Math.abs(dy)>=Math.abs(dx)||canScrollHorizontally)){

startScroll=true;

}

return startScroll&&super.onInterceptTouchEvent(e);

}

return super.onInterceptTouchEvent(e);

}

default:

return super.onInterceptTouchEvent(e);

}

}

}

```

其他问题

当用户快速滑动(fling)RecyclerView 的时候, RecyclerView 需要一段时间来确定其最终位置。 如果用户在快速滑动一个子的水平 RecyclerView,在子 RecyclerView 还在滑动的过程中,如果用户垂直滑动,则是无法垂直滑动的。原因是子 RecyclerView 依然处理了这个垂直滑动事件。

所以,在快速滑动后的滚动到静止的状态中,子 View 不应该响应滑动事件了,再次看看 RecyclerView 的 onInterceptTouchEvent() 代码:

Java

```

@Override

public boolean onInterceptTouchEvent(MotionEvente){

...

switch(action){

case MotionEvent.ACTION_DOWN:

...

if(mScrollState==SCROLL_STATE_SETTLING){

getParent().requestDisallowInterceptTouchEvent(true);

setScrollState(SCROLL_STATE_DRAGGING);

}

...

}

return mScrollState==SCROLL_STATE_DRAGGING;

}

```

可以看到,当 RecyclerView 的状态为 SCROLL_STATE_SETTLING (快速滑动后到滑动静止之间的状态)时, RecyclerView 告诉父控件不要拦截事件。

同样的,如果只有一个方向固定,这样处理是没问题的。

针对我们这个嵌套的情况,父 RecyclerView 应该只拦截垂直滚动事件,所以可以这么修改父 RecyclerView:

Java

```

public class FeedRootRecyclerViewextendsBetterRecyclerView{

public FeedRootRecyclerView(Contextcontext){

this(context,null);

}

public FeedRootRecyclerView(Contextcontext,@NullableAttributeSetattrs){

this(context,attrs,0);

}

public FeedRootRecyclerView(Contextcontext,@NullableAttributeSetattrs,intdefStyle){

super(context,attrs,defStyle);

}

@Override

public void requestDisallowInterceptTouchEvent(booleandisallowIntercept){

/* do nothing */

}

}

```

下图为最终的结果:

如果感兴趣可以下载示例项目,注意示例项目中使用 kotlin,所以需要配置 kotlin 插件。

原文:http://nerds.headout.com/fix-horizontal-scrolling-in-your-android-app/

Read more:http://blog.chengyunfeng.com/?p=1017#ixzz4KD0L8Ny5

上一篇下一篇

猜你喜欢

热点阅读