RecyclerView 上拉加载更多及滚动到底部的判断

1. 根据 item 判断是否到达底部
这种方法最常见,一般都是像下面这样实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static boolean isVisBottom(RecyclerView recyclerView){ 
  LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); 
  //屏幕中最后一个可见子项的position
  int lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition(); 
  //当前屏幕所看到的子项个数
  int visibleItemCount = layoutManager.getChildCount(); 
  //当前RecyclerView的所有子项个数
  int totalItemCount = layoutManager.getItemCount(); 
  //RecyclerView的滑动状态
  int state = recyclerView.getScrollState(); 
  if(visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1 && state == recyclerView.SCROLL_STATE_IDLE){  
     return true;
  }else {  
     return false
  }
}

用这种方式判断是否滚动到底部时,只要最后一个 item 显示出一点,就会触发加载更多,用户此时看不到在 FooterView 处的 加载更多 字样(与拖拽展示出加载更多的需求不符);另外,当 RecyclerView 的 item 过少不足填满整个 RecyclerView 时,也会触发 加载更多 ;因此,这种方式不符合我们的要求。

2. 使用 canScrollVertically(int direction) 判断是否到达底部

1
2
RecyclerView.canScrollVertically(1)的值表示是否能向上滚动,false表示已经滚动到底部
RecyclerView.canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部

这种方法看似简单,其实同样存在一些陷阱。当 RecyclerView 的 item 过少不足填满整个 RecyclerView 时,无论上拉还是下拉都会触发加载更多;另外,direction 不只可取1和-1,只需保证正负就能达到一样的效果。

1
2
3
4
5
6
7
8
9
10
11
// View#canScrollVertically(int direction) 源码
public boolean canScrollVertically(int direction) {
    final int offset = computeVerticalScrollOffset();
    final int range = computeVerticalScrollRange() - computeVerticalScrollExtent();
    if (range == 0) return false;
    if (direction < 0) {
        return offset > 0;
    } else {
        return offset < range - 1;
    }
}

3. 通过 LinearLayoutManager 进行一系列的计算
这种方法极不推荐使用,过程很复杂,不过对于理解 View 的布局有很大的帮助。这种方法共分为四步,下面将网上的方法抄录如下(请读者自行验证是否也存在方法1,2同样的问题):

算出一个子项的高度

public static int getItemHeight(RecyclerView recyclerView) {
int itemHeight = 0;
View child = null;
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int firstPos = layoutManager.findFirstCompletelyVisibleItemPosition();
int lastPos = layoutManager.findLastCompletelyVisibleItemPosition();
child = layoutManager.findViewByPosition(lastPos);
if (child != null) {
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
itemHeight = child.getHeight() + params.topMargin + params.bottomMargin;
}
return itemHeight;
}

算出滑过的子项的总距离

public static int getLinearScrollY(RecyclerView recyclerView) {
int scrollY = 0;
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
int headerCildHeight = getHeaderHeight(recyclerView);
int firstPos = layoutManager.findFirstVisibleItemPosition();
View child = layoutManager.findViewByPosition(firstPos);
int itemHeight = getItemHeight(recyclerView);
if (child != null) {
int firstItemBottom = layoutManager.getDecoratedBottom(child);
scrollY = headerCildHeight + itemHeight * firstPos - firstItemBottom;
if(scrollY < 0){
scrollY = 0;
}
}
return scrollY;
}

算出所有子项的总高度

public static int getLinearTotalHeight(RecyclerView recyclerView) { int totalHeight = 0;
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
View child = layoutManager.findViewByPosition(layoutManager.findFirstVisibleItemPosition());
int headerCildHeight = getHeaderHeight(recyclerView);
if (child != null) {
int itemHeight = getItemHeight(recyclerView);
int childCount = layoutManager.getItemCount();
totalHeight = headerCildHeight + (childCount - 1) * itemHeight;
}
return totalHeight;
}

高度作比较

public static boolean isLinearBottom(RecyclerView recyclerView) {
boolean isBottom = true;
int scrollY = getLinearScrollY(recyclerView);
int totalHeight = getLinearTotalHeight(recyclerView);
int height = recyclerView.getHeight();
// Log.e(“height”,”scrollY ” + scrollY + ” totalHeight ” + totalHeight + ” recyclerHeight ” + height);
if (scrollY + height < totalHeight) {
isBottom = false;
}
return isBottom;
}

判断 RecyclerView 拖拽

这一步比较简单,直接监听滚动即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
RecyclerView.addOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == SCROLL_STATE_DRAGGING) {
                // 拖拽状态,实际使用中还需要判断 加载更多 是否已显示
            }
        }
 
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
        }
    });

推荐方法

该方法在 View#canScrollVertically(int direction) 的基础上,针对上拉拖拽且有可能 items 没有填充满整个 RecyclerView 这个场景做了优化,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
RecyclerView.addOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == SCROLL_STATE_DRAGGING && 没有触发加载更多) {
                if (RecyclerView.computeVerticalScrollOffset() > 0) {// 有滚动距离,说明可以加载更多,解决了 items 不能充满 RecyclerView
                的问题及滑动方向问题
                    boolean isBottom = false ;
                    isBottom = RecyclerView.computeVerticalScrollExtent()
                        + RecyclerView.computeVerticalScrollOffset()
                        == RecyclerView.computeVerticalScrollRange() ;
                    // 也可以使用 方法2
                    // isBottom = !RecyclerView.canScrollVertically(1) ;
                    if (isBottom) {
                        // 说明滚动到底部,触发加载更多
                        ...
                    }
                }
            }
        }
 
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
        }
    });
posted @ 2019-07-09 17:40  天涯海角路  阅读(818)  评论(0)    收藏  举报