手把手教你實現RecyclerView的下拉刷新和上拉加載更多


手把手教你實現RecyclerView的下拉刷新和上拉加載更多

版權聲明:本文為博主原創文章,遵循 CC 4.0 by-sa 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接: https://blog.csdn.net/u012402124/article/details/78210639

個人原創,轉載請注明出處http://blog.csdn.net/u012402124/article/details/78210639

2018年10月25日更新


讓大家花費時間看文章卻沒有解決需求,隨着bug的增多內心的愧疚感逐漸增強,但幾個月前的代碼確實看不下去,bug也不想解決了,於是直接重寫了。
實現思路未變,依舊是監聽滑動,根據位置做出處理,但實現方式做了修改,相比於之前的方式使用起來更加簡單。

暫時只支持LinearLayout,不支持GridLayoutManager和StaggeredLayoutManager

使用方式

  1. 布局文件,如下,不多說:

    <com.oy.wrapperrecyclerview.widget.xRecyclerView
        android:id="@+id/gank_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:overScrollMode="never"/>
    
  2. Adapter不能再繼承RecyclerView.Adapter,需要繼承自xRecyclerView.xAdapter,同時實現三個方法,getxItemCountonCreatexViewHolderonBindxViewHolder,按照實現RecyclerView.Adapter中的getItemCountonCreateViewHolderonBindViewHolder的方式即可。

  3. 將2中xRecyclerView.xAdapter的實現類的實例通過xRecyclerView.setAdapter設置進去。

  4. 通過xRecyclerView.setListener監聽下拉刷新和上拉加載動作。

  5. 下拉刷新或上拉加載完成后需要調用xRecyclerView.stopRefreshingxRecyclerView.stopLoadingMore來更新顯示狀態。

一共這5步,簡化了不少。因為還不支持GridLayoutManager所以把原來看圖片的網格布局改成線性的了,不過話說回來看大圖里的妹子真不錯 😛。

代碼已更新到Github,如有問題:oynix@foxmail.com

以下為原文


縱觀多數App,下拉刷新和上拉加載更多是很常見的功能,但是谷歌官方只有一個SwipeRefreshLayout用來下拉刷新,上拉加載更多還要自己做。

基於RecyclerView簡單封裝了這兩個操作,下拉刷新支持LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager;上拉加載更多只支持前兩者。


話不多說先上效果圖 數據來自干貨集中營

下拉刷新
(下拉刷新)

上拉加載更多 -- LinearLayoutManager
(上拉加載更多 – LinearLayoutManager)

上拉加載更多 -- GridLayoutManager
(上拉加載更多 – GridLayoutManager)


(一) 使用方式,很簡單 如下:

  • 1. 下拉刷新 3步走

1.1 布局文件

// 用SwipeRefreshLayout包裹RecyclerView
<android.support.v4.widget.SwipeRefreshLayout android:id="@+id/gank_swipe_refresh_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/gank_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:overScrollMode="never"/> </android.support.v4.widget.SwipeRefreshLayout> 

1.2 給SwipeRefreshLayout 添加監聽 增加觸發刷新時的操作(比如重新請求數據)

SwipeRefreshLayout swipeRefreshLayout = findViewById(); swipeRefreshLayout.setOnRefreshListener(new swipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { // do something, such as re-request from server or other } }); 

1.3 刷新操作(重新請求數據)完成后要回調來停止隱藏刷新動畫(中上方圓形懸浮進度條旋轉動畫)

swipeRefreshLayout.setRefreshing(false); 
  • 1

至此下拉刷新完成

  • 2.上拉加載3步走

2.1 初始化AdapterWrapperSwipeToLoadHelper

// adapter是你自己為RecyclerView寫的Adapter RecyclerView.Adapter adapter = new YourOwnAdapter(); AdapterWrapper adapterWrapper = new AdapterWrapper(adapter); RecyclerView recyclerView = findViewById(); // 將RecyclerView和剛創建的adapterWrapper傳入 SwipeToLoadHelper helper = new SwipeToLoadHelper(recyclerView, adapterWrapper); 

2.2 設置加載動作觸發后的監聽

helper.setLoadMoreListener(new SwipeToLoadHelper.LoadMoreListener() { @Override public void onLoad() { // do something, such as request more data from server or other. } }) 

2.3 加載更多內容完成后要回調方法停止動畫

helper.setLoadMoreFinish() 
  • 1

至此上拉加載完成 (注意更新數據時要調用AdapterWrapper.notifyDataSetChanged)


(二) 簡明扼要的實現思路(上拉加載操作)

RecyclerViewitemView的顯示情況分為四種:

  1. 第1個可見的(部分顯示或者完全顯示都算可見)
  2. 第1個可見的且是完整的(完全顯示算作完整的)
  3. 最后1個可見的
  4. 最后1個可見的且是完整的
  • 1. 回彈效果
    即手指抬起滑動停止,上拉加載更多部分顯示時,將上拉加載更多滾動到不顯示,使上面挨着它的itemView為最后1個可見且是最后1個完整可見。

    監聽RecyclerView的滾動,當RecyclerView處於SCROLL_STATE_IDLE 狀態時,獲取最后1個完整可見的itemView:如果是倒數第2個item則計算該item的下邊距到RecyclerView底部的距離deltaY,然后將RecyclerView向下滾動deltaY;如果是上拉加載更多則觸發加載操作;其他情況不用處理。
// 關鍵代碼 rv : recyclerView
int lcp= layoutManager.findLastCompletelyVisibleItemPosition();
if (lcp == layoutManager.getItemCount() - 2) {
	// 倒數第2項
	int fcp= layoutManager.findFirstCompletelyVisibleItemPosition();
    View child = layoutManager.findViewByPosition(lcp);
    int deltaY = rv.getBottom() - rv.getPaddingBottom() - 
				    child.getBottom();
	// fcp為0時說明列表滾動到了頂部, 不再滾動
    if (deltaY > 0 && fcp!= 0) {
	      rv.smoothScrollBy(0, -deltaY);
    }
} else if (lcp== layoutManager.getItemCount() - 1) {
    // 最后一項完全顯示, 觸發操作, 執行加載更多操作
    if (listener!= null) {
	    listener.onLoad();
    }
}

  • 2. 添加底部加載更多itemView

2.1 AdapterWrapper重寫了getItemCount方法,保證得到itemView的數量包括加載更多。當是LinearLayoutManager類型時直接加1;當是GridLayoutManager類型時,如果需要則先將列表最后一行填滿,再加1。比如:列表每行有3個itemView,最后一行只有1個,這時就需要先加2,再加1,來保證加載更多占據完整的一行。

// 關鍵代碼 其中的adapter為構造函數中傳入的原生RecyclerView.Adapter if (adapterType == ADAPTER_TYPE_LINEAR) { // 線性布局 return adapter.getItemCount() + 1; } else { // 網格布局 spanCount為每行itemView的個數 int remain = adapter.getItemCount() % spanCount; // 余數 if (remain == 0) { return adapter.getItemCount() + 1; } // 余數不為0時,先湊滿再加1 return adapter.getItemCount() + 1 + (spanCount - remain); } 

2.2 AdapterWrapper重寫了getItemViewType方法,當是最后一個位置時返回ITEM_TYPE_LOAD

// 關鍵代碼 public int getItemViewType(int position) { // 位置是最后一個時, wrapper進行攔截 if (osition == getItemCount() - 1) { return ITEM_TYPE_LOAD;// 要避免和原生adapter返回值可能重復 } // 其他情況交給原生adapter處理 return adapter.getItemViewType(position); } 

2.3 AdapterWrapper重寫了onCreateViewHolder方法,當類型為ITEM_TYPE_LOAD時返回加載更多ViewHolder,其他情況交給原生的adapter處理。

// 關鍵代碼 public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == ITEM_TYPE_LOAD) { return new LoadMoreHolder(); } else { return adapter.onCreateViewHolder(parent, viewType); } } 

2.4 AdapterWrapper重寫了onBindViewHolder,這里有三種可能的情況:1. 正常的數據項itemView,交給adapter處理;2. GridView的空白itemView,隱藏處理;3. 底部的加載更多,目前不需要做什么處理。

// 關鍵代碼 public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if (position == getItemCount() - 1) { } else if (position < adapter.getItemCount()){ adapter.onBindViewHolder(holder, position); } else { holder.itemView.setVisibility(View.INVISIBLE); } } 

(三) 額外的兩個說明

  • SwipeFreshLayout有個setEnable(boolean)方法,設置為false的時候就下拉刷新功能就沒有了,等同於普通的RecyclerView
  • 同樣SwipeToLoadHelper有個setSwipeToLoadEnabled(boolean)方法,設置為false的時候上拉加載功能就沒有了, 等同於普通的RecyclerView

如有問題,歡迎指正~


附[項目倉庫地址](https://github.com/oynix/wraprecyclerview),如有需要請自取~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM