優雅地為RecyclerView加上頭部、下拉重新整理、自動載入
一、概述
我們在寫專案的時候,永遠都離不開ListView、RecyclerView這類的控制元件,幾乎是在任何的APP中都可以看到他們的影子,但是RecyclerView並沒有像ListView提供了addHeadView、addFooterView這樣的原生方法,更沒有提供onItemclicklistener這樣的方法,所有這樣方法需要我們自己手動實現。在這裡吐槽一個谷歌。不知道谷歌是怎麼想的。
下拉重新整理跟載入更多更是我們隨處可以見的。但是有些App要上拉一下才能載入更多,我覺得讓使用者多了一步這樣的操作,感覺完全是沒有必要的,使用者體驗並不好。如果不自動載入更多是因為使用者的流量,我就這種擔心完全是沒有必要的。所以我認為上拉載入更多這樣的功能是非常雞肋的,滑到底部正常就應該自動載入更多。
二、原理
既然谷歌沒有為RecyclerView 沒有提供這樣方法,那我們只能自己手動實現了。
根據item_type型別不同來實現給RecyclerView加頭部的效果
判斷是否滑動到最後一條來實現載入更多,其實也是根據item_type不同來實現
下拉重新整理谷歌有原生的swiperefreshlayout
三、效果圖
載入更多有三中情況
載入中、載入完成
載入失敗、點選重試
載入完成並沒有更多資料了
四、程式碼
public abstract class BaseAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public static final int TYPE_FOOTER_VIEW = 100002;//footer型別 Item protected Context mContext; private OnLoadMoreListener mLoadMoreListener;//載入更多回調 private View mLoadingView; //載入中 private View mLoadFailedView; //載入失敗 private View mLoadEndView; //載入完成 private RelativeLayout mFooterLayout;//載入佈局 protected List<T> mDatas;//資料來源 private boolean mOpenLoadMore;//是否開啟載入更多 private boolean isAutoLoadMore = true;//是否自動載入,當資料不滿一螢幕會自動載入 private boolean isAddHead;//是否根據Item type新增頭 protected abstract int getViewType(int position, T data); public BaseAdapter(Context context, List<T> datas, boolean isOpenLoadMore) { mContext = context; mDatas = datas == null ? new ArrayList<T>() : datas; mOpenLoadMore = isOpenLoadMore; } @Override public CommonViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { CommonViewHolder viewHolder = null; switch (viewType) { case TYPE_FOOTER_VIEW: if (mFooterLayout == null) { mFooterLayout = new RelativeLayout(mContext); } viewHolder = CommonViewHolder.create(mFooterLayout); break; } return viewHolder; } @Override public int getItemCount() { if (mDatas.isEmpty()) { return 0; } return mDatas.size() + getFooterViewCount(); } @Override public int getItemViewType(int position) { if (isFooterView(position)) { return TYPE_FOOTER_VIEW; } return getViewType(position, mDatas.get(position)); } /** * 根據positiond得到data * * @param position * @return */ public T getItem(int position) { if (mDatas.isEmpty()) { return null; } return mDatas.get(position); } /** * 是否是FooterView * * @param position * @return */ private boolean isFooterView(int position) { return mOpenLoadMore && position >= getItemCount() - 1; } protected boolean isCommonItemView(int viewType) { return viewType != TYPE_FOOTER_VIEW; } /** * StaggeredGridLayoutManager模式時,FooterView可佔據一行 * * @param holder */ @Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { super.onViewAttachedToWindow(holder); if (isFooterView(holder.getLayoutPosition())) { ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true); } } } /** * GridLayoutManager模式時, FooterView可佔據一行,判斷RecyclerView是否到達底部 * * @param recyclerView */ @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); if (layoutManager instanceof GridLayoutManager) { final GridLayoutManager gridManager = ((GridLayoutManager) layoutManager); gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if (isFooterView(position)) { return gridManager.getSpanCount(); } return 1; } }); } startLoadMore(recyclerView, layoutManager); } /** * 判斷列表是否滑動到底部 * * @param recyclerView * @param layoutManager */ private void startLoadMore(RecyclerView recyclerView, final RecyclerView.LayoutManager layoutManager) { if (!mOpenLoadMore || mLoadMoreListener == null) { return; } recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { super.onScrollStateChanged(recyclerView, newState); if (newState == RecyclerView.SCROLL_STATE_IDLE) { if (!isAutoLoadMore && findLastVisibleItemPosition(layoutManager) + 1 == getItemCount()) { scrollLoadMore(); } } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (isAutoLoadMore && findLastVisibleItemPosition(layoutManager) + 1 == getItemCount()) { scrollLoadMore(); } else if (isAutoLoadMore) { isAutoLoadMore = false; } } }); } /** * 到達底部開始載入更多 */ private void scrollLoadMore() { if (mFooterLayout.getChildAt(0) == mLoadingView) { if (mLoadMoreListener != null) { mLoadMoreListener.onLoadMore(false); } } } private int findLastVisibleItemPosition(RecyclerView.LayoutManager layoutManager) { if (layoutManager instanceof LinearLayoutManager) { return ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); } else if (layoutManager instanceof StaggeredGridLayoutManager) { int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(null); return Util.findMax(lastVisibleItemPositions); } return -1; } /** * 清空footer view */ private void removeFooterView() { mFooterLayout.removeAllViews(); } /** * 新增新的footer view * * @param footerView */ private void addFooterView(View footerView) { if (footerView == null) { return; } if (mFooterLayout == null) { mFooterLayout = new RelativeLayout(mContext); } removeFooterView(); RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); mFooterLayout.addView(footerView, params); } /** * 載入更多的資料 * * @param datas */ public void setLoadMoreData(List<T> datas) { int size = mDatas.size(); mDatas.addAll(datas); notifyItemInserted(size); } /** * 下拉重新整理 * * @param datas */ public void setData(List<T> datas) { mDatas.addAll(0, datas); notifyDataSetChanged(); } /** * 初始化資料來源 * * @param datas */ public void setInitData(List<T> datas) { mDatas.clear(); if (isAddHead) { mDatas.add(null);//讓資料來源第一條為null來新增頭部 } mDatas.addAll(datas); notifyDataSetChanged(); } public void remove(int position) { mDatas.remove(position); notifyDataSetChanged(); } /** * 是否跟新增頭部 * * @param b */ public void setAddHaed(boolean b) { isAddHead = b; } /** * 初始化載入中佈局 * * @param loadingView */ public void setLoadingView(View loadingView) { mLoadingView = loadingView; addFooterView(mLoadingView); } public void setLoadingView(int loadingId) { setLoadingView(Util.inflate(mContext, loadingId)); } /** * 初始化載入失敗佈局 * * @param loadFailedView */ public void setLoadFailedView(View loadFailedView) { if (loadFailedView == null) { return; } mLoadFailedView = loadFailedView; addFooterView(mLoadFailedView); mLoadFailedView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { addFooterView(mLoadingView); if (mLoadMoreListener != null) { mLoadMoreListener.onLoadMore(true); } } }); } public void setLoadFailedView(int loadFailedId) { setLoadFailedView(Util.inflate(mContext, loadFailedId)); } /** * 初始化載入完成佈局 * * @param loadEndView */ public void setLoadEndView(View loadEndView) { mLoadEndView = loadEndView; addFooterView(mLoadEndView); } public void setLoadEndView(int loadEndId) { setLoadEndView(Util.inflate(mContext, loadEndId)); } /** * 返回foot數量 * * @return */ public int getFooterViewCount() { return mOpenLoadMore && !mDatas.isEmpty() ? 1 : 0; } public void setOnLoadMoreListener(OnLoadMoreListener loadMoreListener) { mLoadMoreListener = loadMoreListener; } }
我們封裝一個BaseAdapter,裡面主要處理了一些判斷是否滑動底部,新增腳、新增頭等操作,程式碼註釋的很清楚,大家讀一下就知道哪個方法是幹什麼的了
public class LoadMoreAdapter extends MultiTypeBaseAdapter<String> { public LoadMoreAdapter(Context context, List<String> datas, boolean isOpenLoadMore) { super(context, datas, isOpenLoadMore); } @Override protected void convert(CommonViewHolder holder, final String data, int viewType) { if (viewType == 0) { } else { holder.setText(R.id.text1, data); } } @Override protected int getItemLayoutId(int viewType) { if (viewType == 0) { return R.layout.item_head; } return R.layout.item_layout1; } @Override protected int getViewType(int position, String data) { if (data == null) { return 0; } else { return 1; } } }
這個是LoadMoreAdapter,裡面根據item_type來判斷是否要載入哪個佈局,一個是載入頭部的佈局、一個是載入正常佈局。
public class MainActivity extends AppCompatActivity {
private LoadMoreAdapter mAdapter;
private RecyclerView mRecyclerView;
private boolean isFailed = true;
private SwipeRefreshLayout mSwipeRefreshLayout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe);
mSwipeRefreshLayout.setColorSchemeColors(Color.RED, Color.BLUE);
//初始化adapter
mAdapter = new LoadMoreAdapter(this, null, true);
mAdapter.setAddHaed(true);//設定有頭部
//點選事件
mAdapter.setOnMultiTypeItemClickListener(new OnMultiTypeItemClickListeners<String>() {
@Override
public void onItemClick(CommonViewHolder viewHolder, String data, int position, int viewType) {
if(data == null){
Toast.makeText(MainActivity.this, "我是頭部", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(MainActivity.this, data, Toast.LENGTH_SHORT).show();
}
}
});
//重新整理資料監聽
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
loadData();
}
});
//載入更多事件
mAdapter.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore(boolean isReload) {
loadMore();
}
});
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mAdapter);
loadData();//初始化資料
}
/**
* 初始化資料
*/
private void loadData() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
List<String> data = new ArrayList<>();
for (int i = 0; i < 12; i++) {
data.add(i+"");
}
//重新整理資料
mAdapter.setInitData(data);
mSwipeRefreshLayout.setRefreshing(false);
mAdapter.setLoadingView(R.layout.load_loading);
}
}, 2000);
}
private void loadMore() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (mAdapter.getItemCount() > 30 && isFailed) {
isFailed = false;
//載入失敗
mAdapter.setLoadFailedView(R.layout.load_failed);
} else if (mAdapter.getItemCount() > 50) {
//載入完成
mAdapter.setLoadEndView(R.layout.load_end);
} else {
final List<String> data = new ArrayList<>();
for (int i = 0; i < 12; i++) {
data.add("我是載入更多" + i);
}
mAdapter.setLoadMoreData(data);
}
}
}, 3000);
}
}
這個是Activity裡面的程式碼,裡面有下拉重新整理,載入更多的邏輯 。大概就這樣子,就可以實現下拉重新整理,自動載入更多、新增頭部功能。
五、原始碼
六、歡迎大家訪問我的網站和我的公眾號
極客導航—程式設計師自己的導航網站
歡迎關注我的公眾號