手把手教你實現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),如有需要請自取~