手把手教你實現RecyclerView的下拉刷新和上拉加載更多
個人原創,轉載請注明出處http://blog.csdn.net/u012402124/article/details/78210639
2018年10月25日更新
讓大家花費時間看文章卻沒有解決需求,隨着bug的增多內心的愧疚感逐漸增強,但幾個月前的代碼確實看不下去,bug也不想解決了,於是直接重寫了。
實現思路未變,依舊是監聽滑動,根據位置做出處理,但實現方式做了修改,相比於之前的方式使用起來更加簡單。
暫時只支持LinearLayout,不支持GridLayoutManager和StaggeredLayoutManager
使用方式
-
布局文件,如下,不多說:
<com.oy.wrapperrecyclerview.widget.xRecyclerView android:id="@+id/gank_recycler_view" android:layout_width="match_parent" android:layout_height="match_parent" android:overScrollMode="never"/> -
Adapter不能再繼承
RecyclerView.Adapter,需要繼承自xRecyclerView.xAdapter,同時實現三個方法,getxItemCount、onCreatexViewHolder和onBindxViewHolder,按照實現RecyclerView.Adapter中的getItemCount、onCreateViewHolder和onBindViewHolder的方式即可。 -
將2中
xRecyclerView.xAdapter的實現類的實例通過xRecyclerView.setAdapter設置進去。 -
通過
xRecyclerView.setListener監聽下拉刷新和上拉加載動作。 -
下拉刷新或上拉加載完成后需要調用
xRecyclerView.stopRefreshing或xRecyclerView.stopLoadingMore來更新顯示狀態。
一共這5步,簡化了不少。因為還不支持GridLayoutManager所以把原來看圖片的網格布局改成線性的了,不過話說回來看大圖里的妹子真不錯 😛。
代碼已更新到Github,如有問題:oynix@foxmail.com
以下為原文
縱觀多數App,下拉刷新和上拉加載更多是很常見的功能,但是谷歌官方只有一個SwipeRefreshLayout用來下拉刷新,上拉加載更多還要自己做。
基於RecyclerView簡單封裝了這兩個操作,下拉刷新支持LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager;上拉加載更多只支持前兩者。
話不多說先上效果圖 數據來自干貨集中營

(下拉刷新)

(上拉加載更多 – LinearLayoutManager)

(上拉加載更多 – 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 初始化AdapterWrapper和SwipeToLoadHelper
// 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)
(二) 簡明扼要的實現思路(上拉加載操作)
RecyclerView的itemView的顯示情況分為四種:
- 第1個可見的(部分顯示或者完全顯示都算可見)
- 第1個可見的且是完整的(完全顯示算作完整的)
- 最后1個可見的
- 最后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),如有需要請自取~
