实现 RecyclerView 嵌套平滑滚动

列表嵌套滑动,关键点在于如何分派事件给父列表还是子列表。

具体节点如下:
1.当父类可以向下滑动时,事件应该交给父类。
2.当父类不能再向下滑动时,事件再交给子类。
3.向上滑动时,如果滑动区域是在子类上,事件首先交给子类,如不在,事件则交给父类。
4.向上滑动时,如子类已经滑动到顶,则事件交给父类。

这里为了方便父子类事件交替,父子间要相互持有引用。为了简化引用赋值,我的思路是在子类初始化时,让其自己去寻找嵌套的父类,并完成父类对子类的引用赋值。

//继承 RecyclerView 在构造方法中调用
postDelayed(new Runnable(){
    @Override
     public void run(){
         if(isNestedScrollingEnabled()) {
             ViewParent p = getParent();
             while(p != null) {
                 //这里不要求父子要相邻嵌套,可以隔个几层,但最好还是相邻。
                 //思路就是沿着视图树一直往上找。
                 if(p instanceof ParentRecyclerView) {
                   mParentView = (ParentRecyclerView)p;
                   mParentView.setNestedChildView(ChildRecyclerView.this);
                 }
                 p = p.getParent();
             }
         }
     }
}, 50);

针对上述4个节点的具体实现思路

1.在父类消耗事件时要时刻留意能否继续向下滑动,如不能就要及时让子类去滑动。

@Override
public boolean onTouchEvent(MotionEvent e) {
    if(lastY = 0) {
        lastY = e.getY();
    }
     //不能再向下滑动的话,就交给子类
     if(!canScrollVertically(1)) {
         if(childView != null) {
             //自定义标记位,标记能否滑动
             canScrollVertical = false;
             childView.scrollBy(0, (int)(lastY - e.getY()));
         }
     }
     lastY = e.getY();
     return super.onTouchEvent(e);
}

此外,因为每次滑动都会涉及到事件结束,也就是 ACTION_UP 操作,需要在 up 时更新下是否能滑动的标记位。

@Override
public boolean onTouchEvent(MotionEvent e) {
     if(lastY = 0) {
         lastY = e.getY();
     }
     //省略......
     if(MotionEvent.ACTION_UP == e.getAction()) {
         //判断子类是否可以滑动
         canScrollVertical = !getNestedChildViewCanScroll();
     }
     return super.onTouchEvent(e);
}

2.父类不能再向下滑动后,事件自然就交给子类处理了,这个由系统或有更上层的父类完成分发。子类要做的就是事件消费,子类可以上下滑动。

3,4.当子类上滑并且到顶部的时候,如果再滑动子类则应把事件交给父类继续执行。这么做才能保证滑动的连贯性,否则子类上滑到顶后,只有松手再滑才能让父类接着滑动。

@Override
public boolean onTouchEvent(MotionEvent e) {
    //不能再向上滑动时,要交出事件
    if(!canScrollVertically(-1)) {
        if(parentView != null) {
            //更新父类滑动标记
            parentView.setCanScrollVertically(true);
            //让父类拦截事件
            parentView.requestDisallowInterceptTouceEvent(false);
            //让父类拦截事件时,页面会有一点卡顿,这么做是为了修正一下滑动位移,试了一下就不卡了
            //之前一直在尝试解决如何让事件柔和的交到父类上而不产生卡顿,后来借用节点1的思路尝试,效果还不错
            parentView.scrollBy(0, -8);
         }
    }
}

另外还有一点要注意,如何判断父类能否滑动呢?光靠自身的 canScrollVertically() 是不够的,因为涉及到嵌套,所以要考虑子类,具体的判断依据是

canScrollVertical || childView == null || !childView.canScrollVertically(-1)

在 RecyclerView 消费事件时,最终能否垂直滑动是由 LayoutManager 来决定的,所以要重写 LayoutManager 的 canScrollVerically() 方法,用上面的值去替代。
至此,嵌套滑动的事情我们基本就解决了,接下去要考虑 Fling 效果,这块建议参考
仿京东、淘宝首页,通过两层嵌套的RecyclerView实现tab的吸顶效果
我试了效果还可以,具体原理有待后面分析。

有了滑动和惯性滑动后,RecyclerView 嵌套基本也就可以了,我之所以需要自己去实现,一方面想了解原理,另一方面布局不太合适。因为我的父类 RecyclerView 外使用的是 SmartRefreshLayout 和参考资料里的不太兼容,如果按参考资料里的来,改动又比较大,于是我就做了拆解改造。

问题:因为外层包的是 SmartRefreshLayout,当出现节点 4 的情况时,会出现父类没有上滑,而是触发了下拉刷新。
解决方法:

smartRL.setScrollBoundaryDecider(new ScrollBoundaryDecider(){
     @Override
     public boolean canRefresh(View content) {
         return !recyclerView.canScrollVertically(-1);
     }
     //省略......
});

如果没用 SmartRefreshLayout,换成别的,我想应该也是可以的。

参考文章:
NestedRecyclerView
仿京东、淘宝首页,通过两层嵌套的RecyclerView实现tab的吸顶效果
关于View中mParent的来龙去脉
Android 仿京东,淘宝RecyclerView嵌套ViewPager嵌套RecyclerView商品展示

你可能感兴趣的:(实现 RecyclerView 嵌套平滑滚动)