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

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) 判断是否到达底部

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

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

// 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 拖拽

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

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 这个场景做了优化,代码如下:

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);
        }
    });