效果圖
RecyclerView實現(xiàn)加載更多可分為兩個步驟
- RecyclerView滑動到底部的監(jiān)聽
- 給RecyclerView添加footer鲸匿,展示加載狀態(tài)
一、給RecyclerView添加ScrollListener監(jiān)聽滑動到底部
1. 繼承RecyclerView添加滑動監(jiān)聽
public class LoadMoreRecyclerView extends RecyclerView {
public LoadMoreRecyclerView(Context context) {
this(context, null);
}
public LoadMoreRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadMoreRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
}
}
2. 判斷滑動到底部
2.1 判斷滑動方向
給LoadMoreRecyclerView添加屬性
/**
* 是否是向下滑動
*/
private boolean isScrollDown;
在OnScrollListener中的onScrolled方法中判斷RecyclerView的滑動方向
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
isScrollDown = dy > 0;
}
RecyclerView在滑動完成的時候會調(diào)用onScrolled方法币他,其中dx和dy分別表示水平滑動和垂直滑動的距離
如果dy>0表示向下滑動
2.2 判斷滑動到底部
在OnScrollListener中的onScrollStateChanged方法中判斷RecyclerView是否滑動到底部
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {// RecyclerView已經(jīng)停止滑動
int lastVisibleItem;
// 獲取RecyclerView的LayoutManager
LayoutManager layoutManager = recyclerView.getLayoutManager();
// 獲取到最后一個可見的item
if (layoutManager instanceof LinearLayoutManager) {// 如果是LinearLayoutManager
lastVisibleItem = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {// 如果是StaggeredGridLayoutManager
int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
lastVisibleItem = findMax(into);
} else {// 否則拋出異常
throw new RuntimeException("Unsupported LayoutManager used");
}
// 獲取item的總數(shù)
int totalItemCount = layoutManager.getItemCount();
/*
并且最后一個可見的item為最后一個item
并且是向下滑動
*/
if (lastVisibleItem >= totalItemCount - 1 && isScrollDown) {
// 此處調(diào)用加載更多回調(diào)接口的回調(diào)方法
}
}
}
/**
* 獲取數(shù)組中的最大值
*
* @param lastPositions 需要找到最大值的數(shù)組
* @return 數(shù)組中的最大值
*/
private int findMax(int[] lastPositions) {
int max = lastPositions[0];
for (int value : lastPositions) {
if (value > max) {
max = value;
}
}
return max;
}
RecyclerView的滑動狀態(tài)改變時會調(diào)用onScrollStateChanged方法鳍置,其中newState表示RecyclerView的滑動狀態(tài)
- SCROLL_STATE_IDLE 表示RecyclerView沒有在滑動
- SCROLL_STATE_DRAGGING 表示RecyclerView正在被拖著滑動
- SCROLL_STATE_SETTLING 表示RecyclerView正在滑動但是沒有外部控制
3. 添加加載更多的回調(diào)接口
3.1 創(chuàng)建加載更多回調(diào)接口
/**
* 加載更多的回調(diào)接口
*/
public interface OnLoadMore {
void onLoad();
}
3.2 給RecyclerView添加設置回調(diào)的方法
/**
* 加載更多的回調(diào)接口
*/
private OnLoadMore mOnLoadMore;
/**
* 是否加載更多
*/
private boolean mIsLoadMore;
/**
* 設置加載更多的回調(diào)接口
* @param onLoadMore 加載更多的回調(diào)接口
*/
public void setOnLoadMore(OnLoadMore onLoadMore) {
// 是否加載更多置為true
this.mIsLoadMore = true;
this.mOnLoadMore = onLoadMore;
}
在判斷滑動到底部的地方調(diào)用回調(diào)接口的回調(diào)方法
二登疗、給RecyclerView添加footer展示加載狀態(tài)
1. 繼承Adapter添加footer
private static class LoadMoreAdapter extends Adapter {
/**
* 添加footer的類型
*/
private static final int TYPE_FOOTER = -1;
/**
* footer的狀態(tài)
*/
protected int mLoadMoreStatus = STATUS_PREPARE;
/**
* footer的點擊事件
*/
protected View.OnClickListener mListener;
/**
* 正常item的adapter
*/
private Adapter mAdapter;
/**
* 是否加載更多
*/
private boolean mIsLoadMore;
/**
* GridLayoutManager
*/
private GridLayoutManager mGridLayoutManager;
public LoadMoreAdapter(Adapter adapter, boolean isLoadMore) {
this.mAdapter = adapter;
this.mIsLoadMore = isLoadMore;
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
this.mGridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
}
}
@Override
public void onViewAttachedToWindow(ViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (mIsLoadMore) {// 如果加載更多
if (mGridLayoutManager != null) {
mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// 當position為最后一項時返回spanCount
return position == getItemCount() - 1 ? mGridLayoutManager.getSpanCount() : 1;
}
});
}
ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
if (params instanceof StaggeredGridLayoutManager.LayoutParams) {
if (holder.getLayoutPosition() == getItemCount() - 1) { // 當position為最后一項時這是FullSpan為true
((StaggeredGridLayoutManager.LayoutParams) params).setFullSpan(true);
}
}
}
}
/**
* 如果是footer類型,創(chuàng)建FooterView
* 否則創(chuàng)建正常的ItemView
*/
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (mIsLoadMore && viewType == TYPE_FOOTER) {
return onCreateFooterViewHolder(parent);
} else {
return mAdapter.onCreateViewHolder(parent, viewType);
}
}
/**
* 如果加載更多且是footer類型,則展示footer
* 否則展示正常的item
*/
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (mIsLoadMore && getItemViewType(position) == TYPE_FOOTER) {
bindFooterItem(holder);
} else {
mAdapter.onBindViewHolder(holder, position);
}
}
/**
* 如果加載更多
* 如果正常的item為0 則不顯示footer,返回0
* 如果正常的item不為0 則返回mAdapter.getItemCount() + 1
* 如果不加載更多
* 返回mAdapter.getItemCount()
*/
@Override
public int getItemCount() {
return mIsLoadMore ? mAdapter.getItemCount() == 0 ? 0 : mAdapter.getItemCount() + 1 : mAdapter.getItemCount();
}
/**
* 如果加載更多且position為最有一個,則返回類型為footer
* 否則返回mAdapter.getItemViewType(position)
*/
@Override
public int getItemViewType(int position) {
if (mIsLoadMore && position == getItemCount() - 1) {
return TYPE_FOOTER;
} else {
return mAdapter.getItemViewType(position);
}
}
/**
* 設置footer的狀態(tài),并通知更改
*/
void setLoadMoreStatus(int status) {
this.mLoadMoreStatus = status;
notifyItemChanged(getItemCount() - 1);
}
/**
* 設置footer的點擊重試事件
* @param listener
*/
public void setRetryListener(View.OnClickListener listener) {
this.mListener = listener;
}
public int getLoadMoreStatus() {
return this.mLoadMoreStatus;
}
/**
* 創(chuàng)建FooterView
*/
public ViewHolder onCreateFooterViewHolder(ViewGroup parent) {
return new FooterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.footer_view_sample, parent, false));
}
/**
* 設置是否加載更多
*/
public void setIsLoadMore(boolean isLoadMore) {
this.mIsLoadMore = isLoadMore;
}
/**
* 展示FooterView
* @param holder
*/
protected void bindFooterItem(ViewHolder holder) {
FooterViewHolder footerViewHolder = (FooterViewHolder) holder;
switch (mLoadMoreStatus) {
case STATUS_LOADING:
holder.itemView.setVisibility(View.VISIBLE);
footerViewHolder.pb.setVisibility(View.VISIBLE);
footerViewHolder.tv.setText("正在加載更多...");
break;
case STATUS_EMPTY:
holder.itemView.setVisibility(View.VISIBLE);
footerViewHolder.pb.setVisibility(View.GONE);
footerViewHolder.tv.setText("沒有更多了");
holder.itemView.setOnClickListener(null);
break;
case STATUS_ERROR:
holder.itemView.setVisibility(View.VISIBLE);
footerViewHolder.pb.setVisibility(View.GONE);
footerViewHolder.tv.setText("加載出錯,點擊重試");
holder.itemView.setOnClickListener(mListener);
break;
case STATUS_PREPARE:
holder.itemView.setVisibility(View.INVISIBLE);
break;
case STATUS_DISMISS:
holder.itemView.setVisibility(GONE);
}
}
}
static class FooterViewHolder extends RecyclerView.ViewHolder {
ProgressBar pb;
TextView tv;
public FooterViewHolder(View itemView) {
super(itemView);
pb = (ProgressBar) itemView.findViewById(R.id.pb_footer_view);
tv = (TextView) itemView.findViewById(R.id.tv_footer_view);
}
}
footer_view_sample.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="horizontal"
android:padding="16dp">
<ProgressBar
android:id="@+id/pb_footer_view"
style="@android:style/Widget.ProgressBar.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_footer_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:gravity="center"
android:text="正在加載更多..." />
</LinearLayout>
2. 設置在GridLayoutManager和StaggeredGridLayoutManager下footer撐滿一行
2.1 GridLayoutManager
重寫Adapter的onAttachedToRecyclerView娱节,獲取GridLayoutManager
/**
* GridLayoutManager
*/
private GridLayoutManager mGridLayoutManager;
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
this.mGridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
}
}
重寫onViewAttachedToWindow挠蛉,給GridLayoutManager設置SpanSizeLookup
@Override
public void onViewAttachedToWindow(ViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (mIsLoadMore) {// 如果加載更多
if (mGridLayoutManager != null) {
mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// 當position為最后一項時返回spanCount
return position == getItemCount() - 1 ? mGridLayoutManager.getSpanCount() : 1;
}
});
}
}
}
2.2 StaggeredGridLayoutManager
重寫onViewAttachedToWindow
@Override
public void onViewAttachedToWindow(ViewHolder holder) {
super.onViewAttachedToWindow(holder);
if (mIsLoadMore) {// 如果加載更多
ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
if (params instanceof StaggeredGridLayoutManager.LayoutParams) {
if (holder.getLayoutPosition() == getItemCount() - 1) { // 當position為最后一項時這是FullSpan為true
((StaggeredGridLayoutManager.LayoutParams) params).setFullSpan(true);
}
}
}
}
2. 在LoadMoreRecyclerView的滑動監(jiān)聽中添加判斷并設置footer狀態(tài)
private void init() {
addOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (mOnLoadMore != null) {// 如果加載更多的回調(diào)接口不為空
if (newState == RecyclerView.SCROLL_STATE_IDLE) {// RecyclerView已經(jīng)停止滑動
...
/*
如果RecyclerView的footer的狀態(tài)為準備中
并且最后一個可見的item為最后一個item
并且是向下滑動
*/
if (mLoadMoreAdapter.getLoadMoreStatus() == STATUS_PREPARE
&& lastVisibleItem >= totalItemCount - 1 && isScrollDown) {
// 設置RecyclerView的footer的狀態(tài)為加載中
mLoadMoreAdapter.setLoadMoreStatus(STATUS_LOADING);
// 觸發(fā)加載更多的回調(diào)方法
mOnLoadMore.onLoad();
}
}
}
}
...
});
}
3. 在LoadMoreRecyclerView中重寫setAdapter方法,設置footer狀態(tài)和點擊事件
private LoadMoreAdapter mLoadMoreAdapter;
public void setAdapter(Adapter adapter) {
this.mLoadMoreAdapter = new LoadMoreAdapter(adapter, mIsLoadMore);
this.mLoadMoreAdapter.setRetryListener(retryListener);
super.setAdapter(mLoadMoreAdapter);
}
/**
* 設置footer的狀態(tài)
*/
public void setLoadMoreStatus(int status) {
if (mLoadMoreAdapter != null) {
mLoadMoreAdapter.setLoadMoreStatus(status);
}
}
/**
* footer的重試點擊事件
*/
View.OnClickListener retryListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
mLoadMoreAdapter.setLoadMoreStatus(STATUS_LOADING);
mOnLoadMore.onLoad();
}
};
** ps: 關(guān)于onViewAttachedToWindow和onViewAttachedToWindow的說明 **
onViewAttachedToWindow方法在RecyclerView調(diào)用setAdapter方法是被調(diào)用
-
onViewAttachedToWindow方法在RecyclerView展示在界面上是被調(diào)用
** 為了保證LoadMoreRecyclerView中setOnLoadMore和setAdapter調(diào)用的無序性肄满,不能在onViewAttachedToWindow方法中設置GridLayoutManager的SpanSizeLookup **
三谴古、使用注意
** 因為重寫了RecyclerView的setAdapter方法质涛,把傳如的adapter包裝之后重新設置,所以在調(diào)用notifyDataSetChanged()等方法時掰担,不能直接用自己創(chuàng)建adapter調(diào)用汇陆,而要使用RecyclerView.getAdapter調(diào)用。**