RecyclerView 嵌套 ViewPager 嵌套 RecyclerView 的一种简易的通用框架类

github地址:(https://github.com/liyuzero/NestedRecyclerView)


示例


1. 已知实现方案

  • AppBarLayout 实现
    优:使用简单
    劣:1、动画无法衔接,会有停顿 2、上半部分视图会一次加载完毕,会造成两个影响,(1)无法走recyclerView的缓存机制,(2)会使该部分视图的曝光逻辑复杂化

  • RecyclerView嵌套ViewPager嵌套RecyclerView实现
    1. 重写外层RecyclerView的dispatchTouch事件分发,以及子RecyclerView的touch事件分发,处理滑动冲突
    2. 依照nestedScroll机制,重写内外层RecyclerView的触摸事件
    优:可以无停滞滑动,内外层recyclerView可以进行正常的回收、绘制
    劣:需要重写内外层RecyclerView,使用不太方便
    3. 【本框架采用】只重写外层RecyclerView的事件分发,屏蔽RecyclerView本身的滑动逻辑,自行编写Scroller实现所有滑动逻辑
    优:可以无停滞滑动,内外层recyclerView可以进行正常的回收、绘制,只有最外层RecyclerView需要使用特定类
    劣:因为是外层RecyclerView实现所有触摸事件分发,所以横滑效果不能完全媲美原版,但是有办法弥补

2. 写该框架的原因

  • 目前没找到成熟的开源嵌套框架,都有各自的问题,基本都是思路性的开源代码,基本都需要重写内外RecyclerView,使用不方便。没有一个可以稳定商用的通用框架,所以,自行实现了嵌套逻辑,并在产品中使用,有问题也能即时修改。

3. 原理

具体原理可分为三部分,1、将触摸事件组(按下到抬起)进行分类处理,并屏蔽RecyclerView自身的滑动, 2、在用户手指抬起时触发fling滑动,3、编写Scroller整合内外RecyclerView滑动,使其有连续性

  1. 滑动事件分组和屏蔽部分
  • 1、触摸事件处理部分,该部分将每组触摸事件(从 down 到 up)类型分为两种,一是水平滑动 SCROLL_HOR,二是竖直滑动 SCROLL_VER,屏蔽RecyclerView自身滑动部分简略概括为:onInterceptTouchEvent,依据事件组类型进行拦截,onTouchEvent 直接返回true
    if (mCurScrollState == SCROLL_VER) {
                //竖直滑动时,对Recycler内容进行滑动,该方法处理了内外RecyclerView滑动切换逻辑
                scrollVer(ev, offsetY);
                return true;
            } else if (mCurScrollState == SCROLL_HOR) {
                // 水平滑动时,外层RecyclerView 不拦截触摸事件,该事件将会传给child
                return false;
            } else if (mCurScrollState == SCROLL_NONE) {
                //触摸事件组分类
                float dx = Math.abs(curX - mDownX);
                float dy = Math.abs(curY - mDownY);
                if (dx <= 0.01f && dy < 0.01f) {
                    return false;
                } else {
                    if (Math.abs(dx) >= mViewConfiguration.getScaledTouchSlop()) {
                        mCurScrollState = SCROLL_HOR;
                    }

                    if (Math.abs(dy) > mViewConfiguration.getScaledTouchSlop()) {
                        mCurScrollState = SCROLL_VER;
                        scrollVer(ev, offsetY);
                        return true;
                    }

                    if (mCurScrollState == SCROLL_HOR) {
                        return false;
                    }
                }
            }
            return false;
  1. 当用户抬起手后,RecyclerView会执行fling滑动,这里用到了Scroller的fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)方法,此处传入用户手指抬起时的操作速度,则Scroller会自动给出动画偏移量,具体如下
       void handleFlingEvent() {
            if (mMinYV == 0) {
                mMinYV = mViewConfiguration.getScaledMinimumFlingVelocity();
            }
            if (mTracker == null) {
                mTracker = VelocityTracker.obtain();
            }
            mTracker.computeCurrentVelocity(1000);
            int initialVelocity = (int) mTracker.getYVelocity();
            if (Math.abs(initialVelocity) > mMinYV) {
                // 由于坐标轴正方向问题,要加负号。
                doFling((int) (-initialVelocity * 0.75f));
            }
        }

        void doFling(int speed) {
            // scroller 执行fling动画
            mPreScrollY = 0;
            mScroller.fling(0, mPreScrollY, 0, speed, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
            post(this);
        }

        //
        @Override
        public void run() {
            //计算当前动画偏移量,执行滑动操作
            boolean finished = !mScroller.computeScrollOffset() || mScroller.isFinished();
            if (!finished) {
                int offsetY = mScroller.getCurrY() - mPreScrollY;
                mPreScrollY = mScroller.getCurrY();
                scrollVer(null, -offsetY);
                post(this);
            } else {
                removeCallbacks(this);
            }
        }
  1. 上面两个逻辑最后都调用了scrollVer这个滑动方法,它处理了内外RecyclerView的具体滑动逻辑
private void scrollVer(MotionEvent ev, float offsetY) {
        if(mIsScrollUp) {
            return;
        }
        if (!(offsetY > 0 && !canScrollVertically(-1))) {
            if (ev != null) {
                //避免特殊情况下子View触摸生效或不连续
                MotionEvent event = MotionEvent.obtain(ev);
                event.setAction(MotionEvent.ACTION_CANCEL);
                try {
                    super.dispatchTouchEvent(event);
                } catch (Exception e) {
                    //
                }
            }
            //滑动内容
            scrollContent(offsetY);
        }
    }

    private void scrollContent(float offsetY) {
        int scrollY = (int) offsetY;
        if (!canScrollVertically(1)) {
            if (mChildRecyclerViewHelper != null) {
                RecyclerView recyclerView = mChildRecyclerViewHelper.getCurRecyclerView();
                if (recyclerView != null) {
                    //关闭嵌套滚动机制,避免与下拉刷新等Nested嵌套框架出现冲突
                    recyclerView.setNestedScrollingEnabled(false);
                }
                if (recyclerView != null && recyclerView.getTag(R.id.nested_recycler_view_inner_recycler_listener) == null) {
                    recyclerView.addOnScrollListener(new OnScrollListener() {
                        @Override
                        public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                            super.onScrollStateChanged(recyclerView, newState);
                            if (mOnScrollListener != null) {
                                mOnScrollListener.onScrollStateChanged(recyclerView, newState);
                            }
                        }

                        @Override
                        public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                            super.onScrolled(recyclerView, dx, dy);
                            if (mOnScrollListener != null) {
                                mOnScrollListener.onScrolled(recyclerView, dx, dy);
                            }
                        }
                    });
                    recyclerView.setTag(R.id.nested_recycler_view_inner_recycler_listener, new Object());
                }
                if (recyclerView != null && recyclerView.getMeasuredHeight() != 0) {
                    //tab内的RecyclerView在顶部
                    try {
                        if (!recyclerView.canScrollVertically(-1)) {
                            if (offsetY > 0) {
                                scrollContentView(scrollY);
                            } else {
                                recyclerView.scrollBy(0, -scrollY);
                            }
                        } else if (!recyclerView.canScrollVertically(1)) {
                            mScrollerManager.abortAnimation();
                            recyclerView.scrollBy(0, -scrollY);
                        } else {
                            //滑动到底部,此时需要滑动tab内的recyclerView
                            recyclerView.scrollBy(0, -scrollY);
                        }
                    } catch (Exception e) {
                        //规避以下错误【底部信息流的view在被detached之后引起,偶先,理论上去掉信息流部分的回收机制也行】:java.lang.NullPointerException: Attempt to read from field 'java.util.ArrayList
                        // android.support.v7.widget.StaggeredGridLayoutManager$Span.mViews' on a null object reference
                    }
                } else {
                    scrollContentView(scrollY);
                }
            } else {
                scrollContentView(scrollY);
            }
        } else {
            scrollContentView(scrollY);
        }
    }

链接:折叠RecyclerView 库地址,该框架支持无限层次,并可以复用:(https://github.com/liyuzero/NestedRecyclerView)

结束

你可能感兴趣的:(android,android)