RecyclerView 嵌套 ViewPager 嵌套 Rec

2020-08-11  本文已影响0人  很多圈

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


示例

带广告的吸顶栏

1. 已知实现方案

2. 写该框架的原因

3. 原理

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

  1. 滑动事件分组和屏蔽部分
    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)

结束

上一篇 下一篇

猜你喜欢

热点阅读