1.寫在前面
本文主要實現(xiàn)的是上拉加載更多功能陶耍,下拉刷新使用的是Google官方的SwipeRefreshLayout控件贾费,因為在實現(xiàn)這個功能的時候走了不少彎路,所以在此記錄下來分享給大家质蕉,先看下效果圖:
2.實現(xiàn)
上拉加載更多功能實際上就是給RecyclerView增加一個FooterView餐禁,然后通過判斷是否滑動到了最后一條Item镀虐,來控制FooterView的顯示和隱藏,接下來我們來看下如何實現(xiàn):
Adapter添加FooterView
小二默穴,上代碼:
public class LoadMoreAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<String> dataList;
// 普通布局
private final int TYPE_ITEM = 1;
// 腳布局
private final int TYPE_FOOTER = 2;
// 當前加載狀態(tài)怔檩,默認為加載完成
private int loadState = 2;
// 正在加載
public final int LOADING = 1;
// 加載完成
public final int LOADING_COMPLETE = 2;
// 加載到底
public final int LOADING_END = 3;
public LoadMoreAdapter(List<String> dataList) {
this.dataList = dataList;
}
@Override
public int getItemViewType(int position) {
// 最后一個item設置為FooterView
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//進行判斷顯示類型,來創(chuàng)建返回不同的View
if (viewType == TYPE_ITEM) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.adapter_recyclerview, parent, false);
return new RecyclerViewHolder(view);
} else if (viewType == TYPE_FOOTER) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_refresh_footer, parent, false);
return new FootViewHolder(view);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof RecyclerViewHolder) {
RecyclerViewHolder recyclerViewHolder = (RecyclerViewHolder) holder;
recyclerViewHolder.tvItem.setText(dataList.get(position));
} else if (holder instanceof FootViewHolder) {
FootViewHolder footViewHolder = (FootViewHolder) holder;
switch (loadState) {
case LOADING: // 正在加載
footViewHolder.pbLoading.setVisibility(View.VISIBLE);
footViewHolder.tvLoading.setVisibility(View.VISIBLE);
footViewHolder.llEnd.setVisibility(View.GONE);
break;
case LOADING_COMPLETE: // 加載完成
footViewHolder.pbLoading.setVisibility(View.INVISIBLE);
footViewHolder.tvLoading.setVisibility(View.INVISIBLE);
footViewHolder.llEnd.setVisibility(View.GONE);
break;
case LOADING_END: // 加載到底
footViewHolder.pbLoading.setVisibility(View.GONE);
footViewHolder.tvLoading.setVisibility(View.GONE);
footViewHolder.llEnd.setVisibility(View.VISIBLE);
break;
default:
break;
}
}
}
@Override
public int getItemCount() {
return dataList.size() + 1;
}
private class RecyclerViewHolder extends RecyclerView.ViewHolder {
TextView tvItem;
RecyclerViewHolder(View itemView) {
super(itemView);
tvItem = (TextView) itemView.findViewById(R.id.tv_item);
}
}
private class FootViewHolder extends RecyclerView.ViewHolder {
ProgressBar pbLoading;
TextView tvLoading;
LinearLayout llEnd;
FootViewHolder(View itemView) {
super(itemView);
pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_loading);
tvLoading = (TextView) itemView.findViewById(R.id.tv_loading);
llEnd = (LinearLayout) itemView.findViewById(R.id.ll_end);
}
}
/**
* 設置上拉加載狀態(tài)
*
* @param loadState 0.正在加載 1.加載完成 2.加載到底
*/
public void setLoadState(int loadState) {
this.loadState = loadState;
notifyDataSetChanged();
}
}
首先定義了布局和數(shù)據(jù)加載狀態(tài)的一些標志蓄诽,然后在getItemViewType方法中設置最后一個Item為FooterView薛训,在onCreateViewHolder方法中根據(jù)viewType來加載不同的布局,最后在onBindViewHolder方法中設置一下加載的狀態(tài)顯示就OK了仑氛,對了乙埃,由于多了一個FooterView,所以要記得在getItemCount方法的返回值中加上1锯岖。
到這里一個線性布局列表的Adapter就完成了介袜,注意,是線性布局列表(只有一列的那種)嚎莉,那網(wǎng)格布局怎么辦米酬,先看下這個Adapter在網(wǎng)格布局中使用會發(fā)生什么:
可以看到加載更多的進度條顯示在了一個Item上,如果想要正常顯示的話趋箩,進度條需要橫跨兩個Item赃额,這該怎么辦呢,別擔心叫确,繼續(xù)往下看:
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// 如果當前是footer的位置跳芳,那么該item占據(jù)2個單元格,正常情況下占據(jù)1個單元格
return getItemViewType(position) == TYPE_FOOTER ? gridManager.getSpanCount() : 1;
}
});
}
}
在Adapter中重寫onAttachedToRecyclerView方法竹勉,首先判斷當前是否為網(wǎng)格布局飞盆,然后給GridLayoutManager設置一個SpanSizeLookup,這是一個抽象類,里面有一個抽象方法getSpanSize吓歇,這個方法的返回值決定了每個Item占據(jù)的單元格數(shù)孽水。
以上文為例,是一個兩列的網(wǎng)格布局城看,如果當前Item是FooterView的話需要占據(jù)兩個單元格才能橫向充滿屏幕女气,所以需要返回2(GridLayoutManager的getSpanCount方法獲取到的是當前一行中單元格的數(shù)量),正常情況下每個Item占據(jù)一個單元格测柠。
RecyclerView設置滑動監(jiān)聽
設置好FooterView之后炼鞠,我們還需要判斷一下什么時候顯示出來,這就需要對RecyclerView設置一下滑動監(jiān)聽轰胁,當滑動到最后一個Item的時候谒主,顯示加載更多UI并且開始請求下一頁列表的數(shù)據(jù),看下代碼:
public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {
//用來標記是否正在向上滑動
private boolean isSlidingUpward = false;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
// 當不滑動時
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
//獲取最后一個完全顯示的itemPosition
int lastItemPosition = manager.findLastCompletelyVisibleItemPosition();
int itemCount = manager.getItemCount();
// 判斷是否滑動到了最后一個item赃阀,并且是向上滑動
if (lastItemPosition == (itemCount - 1) && isSlidingUpward) {
//加載更多
onLoadMore();
}
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// 大于0表示正在向上滑動霎肯,小于等于0表示停止或向下滑動
isSlidingUpward = dy > 0;
}
/**
* 加載更多回調(diào)
*/
public abstract void onLoadMore();
}
代碼中已經(jīng)寫了很全的注釋,重點看下onScrolled這個回調(diào)方法凹耙,里面有dx姿现、dy這兩個參數(shù),當向上滑動的時候dy是大于0的肖抱,向左滑動的時候dx是大于0的备典,反方向滑動則小于0,所以這段代碼稍稍修改一下就可以適用于橫向滑動列表的監(jiān)聽意述。
在Activity中使用
準備工作已經(jīng)完成了提佣,接下來看看如何使用吧:
public class LoadMoreActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private LoadMoreAdapter loadMoreAdapter;
private List<String> dataList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recyclerview);
init();
}
private void init() {
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
// 模擬獲取數(shù)據(jù)
getData();
loadMoreAdapter = new LoadMoreAdapter(dataList);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(loadMoreAdapter);
// 設置加載更多監(jiān)聽
recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener() {
@Override
public void onLoadMore() {
loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING);
if (dataList.size() < 52) {
// 模擬獲取網(wǎng)絡數(shù)據(jù),延時1s
new Timer().schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
getData();
loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING_COMPLETE);
}
});
}
}, 1000);
} else {
// 顯示加載到底的提示
loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING_END);
}
}
});
}
private void getData() {
char letter = 'A';
for (int i = 0; i < 26; i++) {
dataList.add(String.valueOf(letter));
letter++;
}
}
}
調(diào)用RecyclerView的addOnScrollListener方法設置一下加載更多監(jiān)聽荤崇,在onLoadMore回調(diào)方法中拌屏,首先顯示正在加載進度UI,然后模擬獲取網(wǎng)絡數(shù)據(jù)术荤,完成之后隱藏加載進度UI倚喂,加載完兩頁數(shù)據(jù)之后顯示到底了的提示。
3.封裝
到這里瓣戚,我們已經(jīng)完成了RecyclerView的上拉加載更多功能端圈,但是大部分的邏輯都寫在了Adapter中,這樣每寫一個Adapter都要寫一遍加載邏輯子库,這是很不優(yōu)雅的舱权,接下來我們對加載更多功能做一個封裝,使其和Adapter完全解構(gòu)仑嗅,看下代碼:
public class LoadMoreWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private RecyclerView.Adapter adapter;
// 普通布局
private final int TYPE_ITEM = 1;
// 腳布局
private final int TYPE_FOOTER = 2;
// 當前加載狀態(tài)宴倍,默認為加載完成
private int loadState = 2;
// 正在加載
public final int LOADING = 1;
// 加載完成
public final int LOADING_COMPLETE = 2;
// 加載到底
public final int LOADING_END = 3;
public LoadMoreWrapper(RecyclerView.Adapter adapter) {
this.adapter = adapter;
}
@Override
public int getItemViewType(int position) {
// 最后一個item設置為FooterView
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//進行判斷顯示類型张症,來創(chuàng)建返回不同的View
if (viewType == TYPE_FOOTER) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_refresh_footer, parent, false);
return new FootViewHolder(view);
} else {
return adapter.onCreateViewHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof FootViewHolder) {
FootViewHolder footViewHolder = (FootViewHolder) holder;
switch (loadState) {
case LOADING: // 正在加載
footViewHolder.pbLoading.setVisibility(View.VISIBLE);
footViewHolder.tvLoading.setVisibility(View.VISIBLE);
footViewHolder.llEnd.setVisibility(View.GONE);
break;
case LOADING_COMPLETE: // 加載完成
footViewHolder.pbLoading.setVisibility(View.INVISIBLE);
footViewHolder.tvLoading.setVisibility(View.INVISIBLE);
footViewHolder.llEnd.setVisibility(View.GONE);
break;
case LOADING_END: // 加載到底
footViewHolder.pbLoading.setVisibility(View.GONE);
footViewHolder.tvLoading.setVisibility(View.GONE);
footViewHolder.llEnd.setVisibility(View.VISIBLE);
break;
default:
break;
}
} else {
adapter.onBindViewHolder(holder, position);
}
}
@Override
public int getItemCount() {
return adapter.getItemCount() + 1;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// 如果當前是footer的位置,那么該item占據(jù)2個單元格鸵贬,正常情況下占據(jù)1個單元格
return getItemViewType(position) == TYPE_FOOTER ? gridManager.getSpanCount() : 1;
}
});
}
}
private class FootViewHolder extends RecyclerView.ViewHolder {
ProgressBar pbLoading;
TextView tvLoading;
LinearLayout llEnd;
FootViewHolder(View itemView) {
super(itemView);
pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_loading);
tvLoading = (TextView) itemView.findViewById(R.id.tv_loading);
llEnd = (LinearLayout) itemView.findViewById(R.id.ll_end);
}
}
/**
* 設置上拉加載狀態(tài)
*
* @param loadState 0.正在加載 1.加載完成 2.加載到底
*/
public void setLoadState(int loadState) {
this.loadState = loadState;
notifyDataSetChanged();
}
}
乍一看好像和上文中的LoadMoreAdapter沒什么區(qū)別俗他,都是繼承了RecyclerView.Adapter并實現(xiàn)了其中的一些方法,但是仔細看會發(fā)現(xiàn)阔逼,構(gòu)造方法中的參數(shù)變成了RecyclerView.Adapter拯辙,在LoadMoreWrapper中我們只處理加載更多功能相關的邏輯,其他邏輯交由Adapter本身處理颜价,相當于擴展了Adapter的一些功能,嗯诉濒,這種方式還有一個學名周伦,叫【裝飾者模式】。
封裝之后未荒,Adapter中的代碼變成了這樣:
public class LoadMoreWrapperAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<String> dataList;
public LoadMoreWrapperAdapter(List<String> dataList) {
this.dataList = dataList;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.adapter_recyclerview, parent, false);
return new RecyclerViewHolder(view);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
RecyclerViewHolder recyclerViewHolder = (RecyclerViewHolder) holder;
recyclerViewHolder.tvItem.setText(dataList.get(position));
}
@Override
public int getItemCount() {
return dataList.size();
}
private class RecyclerViewHolder extends RecyclerView.ViewHolder {
TextView tvItem;
RecyclerViewHolder(View itemView) {
super(itemView);
tvItem = (TextView) itemView.findViewById(R.id.tv_item);
}
}
}
瞬間減少了一大半专挪,使用起來也很簡單,在原有Adapter的基礎上包上一層就可以了:
LoadMoreWrapperAdapter loadMoreWrapperAdapter = new LoadMoreWrapperAdapter(dataList);
LoadMoreWrapper loadMoreWrapper = new LoadMoreWrapper(loadMoreWrapperAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(loadMoreWrapper);
4.寫在最后
源碼已經(jīng)上傳到GitHub上了片排,歡迎Fork寨腔,覺得還不錯就Start一下吧!
功能已集成至:
《Android開源項目 RecyclerViewHelper 上拉加載更多/頭尾布局/拖拽排序/側(cè)滑刪除/側(cè)滑選擇/萬能分割線》
RecyclerViewHelper:https://github.com/alidili/RecyclerViewHelper