1. 程式人生 > >RecyclerView 上拉載入更多及滾動到底部的判斷(上)

RecyclerView 上拉載入更多及滾動到底部的判斷(上)

關於下拉重新整理上拉載入更多,網上有很多例子;下拉重新整理比較簡單直接使用系統提供 SwipeRefreshLayout 即可,比較麻煩的是上拉載入更多,實現上拉的方法多種多樣,這裡對各個方法總結一下。

需求分析

RecyclerView 滾動到底部後,使用者再往上拖拽(這裡使用場景是拖拽,而不是手指離屏後的自動滾動到底部)時,RecyclerView 展示出 載入更多 的字樣並請求更多的資料,請求成功後更新 RecyclerView ;在預設情況下 RecyclerView 的高度大於可展示高度,即 RecyclerView 沒有展示出全部 item ,可滑動,反之則說明不需要載入更多。

技術點

  • 判斷 RecyclerView 滾動到了底部
  • 判斷 RecyclerView 拖拽

判斷 RecyclerView 滾動到了底部

判斷 RecyclerView 是否到達底部網上流傳著下面幾種方法,這些方法都存在些許問題,最後我們再給出推薦的方式。

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

到這裡,上拉的判斷就處理完成了,下一篇將處理 載入更多 檢視的顯示。

附一張從網上找的原理圖:

這裡寫圖片描述