美文网首页
修复RecyclerView嵌套滚动问题

修复RecyclerView嵌套滚动问题

作者: void_Zhao | 来源:发表于2016-09-14 14:26 被阅读0次

    原文: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

    相关文章

      网友评论

          本文标题:修复RecyclerView嵌套滚动问题

          本文链接:https://www.haomeiwen.com/subject/kjpvettx.html