从源码角度分析RecyclerView监听滑动到底部失效

现在大部分的项目都有这么一个需求,在滑动到列表底部时加载更多,用RecyclerView实现的话基本原理就是利用findLastVisibleItemPosition()方法判断目前视图内的最后一个子项位置是否等于整个recyclerView的最后一个子项,判断的方法在网上流传已经很多了,我在这里贴一下

recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                //当前RecyclerView显示出来的最后一个的item的position
                int lastPosition = -1;
                
                //当前状态为停止滑动状态SCROLL_STATE_IDLE时
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
                    if (layoutManager instanceof GridLayoutManager) {
                        lastPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
                    } else if (layoutManager instanceof LinearLayoutManager) {
                        lastPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
                    } else if (layoutManager instanceof StaggeredGridLayoutManager) {
                        //因为StaggeredGridLayoutManager的特殊性可能导致最后显示的item存在多个,所以这里取到的是一个数组
                        //得到这个数组后再取到数组中position值最大的那个就是最后显示的position值了
                        int[] lastPositions = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
                        ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(lastPositions);
                        lastPosition = findMax(lastPositions);
                    }

                    //时判断界面显示的最后item的position是否等于itemCount总数-1也就是最后一个item的position
                    //如果相等则说明已经滑动到最后了
                    Log.i("recyclerView", ("lastVisiblePosition" + lastPosition + "ItemCount" + (recyclerView.getLayoutManager().getItemCount() - 1)));
                    if (lastPosition == recyclerView.getLayoutManager().getItemCount() - 1) {
                        Toast.makeText(MainActivity.this, "滑到底部了", Toast.LENGTH_SHORT).show();
                    }

                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {

            }

        });

很清楚,没有必要解释了,用起来也很舒服

问题产生

但是当我们在RecyclerView布局外部加上一层NestScrollView 神奇的事情发生了,每次滑动,不管是否已经到达底部,都会显示已经到达底部了。


原因分析

为什么呢?打印日志发现每次findLastVisibleItemPositions()返回的值都是和getItemCount-1是一样一样的博主当时并没有想到NestScrollView嵌套滑动的问题,于是秉持着大神们一言不合看源码的精神,我撸起袖子开始看源码,单步调试后发现跟没有NestScrollView存在的不同是

 /**
     * Returns the adapter position of the last visible view. This position does not include
     * adapter changes that were dispatched after the last layout pass.
     * 

* Note that, this value is not affected by layout orientation or item order traversal. * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter, * not in the layout. *

* If RecyclerView has item decorators, they will be considered in calculations as well. *

* LayoutManager may pre-cache some views that are not necessarily visible. Those views * are ignored in this method. * * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if * there aren't any visible items. * @see #findLastCompletelyVisibleItemPosition() * @see #findFirstVisibleItemPosition() */ public int findLastVisibleItemPosition() { final View child = findOneVisibleChild(getChildCount() - 1, -1, false, true); return child == null ? NO_POSITION : getPosition(child); }


LinearLayoutManager(不管什么manager都一样)下的getChildCount返回的值不一样,有NestScroll嵌套的返回值永远等于getItemCount-1,而没有的则显示当前在屏幕上的item数量,好,问题已经找到了,我们继续追踪下去

/**
         * Return the current number of child views attached to the parent RecyclerView.
         * This does not include child views that were temporarily detached and/or scrapped.
         *
         * @return Number of attached children
         */
        public int getChildCount() {
            return mChildHelper != null ? mChildHelper.getChildCount() : 0;
        }

这是ChildHelper中getChildCount的源码,返回就是 mChildHelper.getChildCount(),继续追踪

/**
     * Returns the number of children that are not hidden.
     *
     * @return Number of children that are not hidden.
     * @see #getChildAt(int)
     */
    int getChildCount() {
        return mCallback.getChildCount() - mHiddenViews.size();
    }
大概意思就是childCount减去隐藏的子项数量,对比发现问题出在Callback的getChildCount方法,这个方法是由RecyclerView重写的,我们看那个方法

@Override
            public int getChildCount() {
                return RecyclerView.this.getChildCount();
            }

可以看到这个方法中调用了recyclerview中的getChildCount方法,继续追踪

/**
     * Returns the number of children in the group.
     *
     * @return a positive integer representing the number of children in
     *         the group
     */
    public int getChildCount() {
        return mChildrenCount;
    }

好这个方法时ViewGroup里面的,直接返回childrenCount,感觉线索就这么断了,博主当时的内心是绝望的,但是这时候也应该想到了,这可能是NestScrollView的问题,因为滑动的时候NestScrollView必须获知自己内部的高度来保证滑动事件的产生,所以他必须一次性加载所有的RecyclerView的子项来计算高度,那么RecyclerView的重用子控件在这里也可以理解为不存在了,所以getChildCount就会一直返回RecyclerView真正拥有的子项。

如何解决

到这里原因就已经明了了,那么怎么解决呢?既然RecyclerView的滑动事件无效了,咱们还有外面的NestScrollView嘛,我们监听NestScrollView的滑动事件就可以了

下面话也不多讲,直接贴代码

NestedScrollView scrollView = (NestedScrollView) findViewById(R.id.main_scroll);
        scrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
            @Override
            public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                if (scrollY > oldScrollY) {
                    Log.i(TAG, "Scroll DOWN");
                }
                if (scrollY < oldScrollY) {
                    Log.i(TAG, "Scroll UP");
                }

                if (scrollY == 0) {
                    Log.i(TAG, "TOP SCROLL");
                }

                if (scrollY == (v.getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) {
                    Log.i(TAG, "BOTTOM SCROLL");
                }
            }
        });

问题成功解决





你可能感兴趣的:(从源码角度分析RecyclerView监听滑动到底部失效)