分頁是Android性能優化和提升用戶體驗的一個重要手段,因此,幾乎在所有項目中,都存在上拉加載更多的功能。目前第三方上拉加載更多的項目五花百門,但是都存在一個缺點,就是,往往我們只需要一個小功能,但是卻不得不導入大量無用代碼,所以何不借助RecyclerView和ListView來實現自己的加載更多控件呢。
需求
- 滑動到最底部時,執行加載更多操作。
- 臨界點問題:當頁面數據沒有超出屏幕時,加載更多功能自動禁止。
- 臨界點問題:當最后一個item可見時就執行加載更多。
RecyclerView 實現加載更多
加載進度條作為recyclerview的最后一項,通過一個封裝AdapterWrapper將實際的Adapter和加載更多的Item結合起來。
// 如果count的大小沒變化,那么AdapterWrapper實際上就是一個mAdapter的代理。
private class AdapterWrapper extends RecyclerView.Adapter{
private AdapterWrapper() {
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType <= 0) {
return loadMoreAdapter.onCreateViewHolder(parent, viewType);
} else {
return mAdapter.onCreateViewHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
if (position < mAdapter.getItemCount()) {
mAdapter.onBindViewHolder(holder, position);
}
else {
loadMoreAdapter.onBindViewHolder(holder, position);
}
}
@Override
public int getItemCount() {
// 如果開啟加載更多功能,那么多增加一項作為加載的進度狀態
if (loadMoreEnable) {
return mAdapter.getItemCount() + 1;
}
else {
return mAdapter.getItemCount();
}
}
@Override
public int getItemViewType(int position) {
if (position < mAdapter.getItemCount()) {
return mAdapter.getItemViewType(position);
}
else {
return loadMoreType;
}
}
@Override
public long getItemId(int position) {
if (position < mAdapter.getItemCount()) {
return mAdapter.getItemId(position);
}
else {
return position;
}
}
@Override
public void onBindViewHolder(ViewHolder holder, int position, List payloads) {
if (position < mAdapter.getItemCount()) {
mAdapter.onBindViewHolder(holder, position, payloads);
}
else {
super.onBindViewHolder(holder, position, payloads);
}
}
}
LoadMoreAdapter
模擬RecyclerView.Adapter的2個核心回調,通過實現LoadMoreAdapter定制加載進度的顯示樣式,如果開啟加載更多,必須設置LoadMoreAdapter。
public static abstract class LoadMoreAdapter {
public abstract ViewHolder onCreateViewHolder(ViewGroup parent, int viewType);
public abstract void onBindViewHolder(ViewHolder holder, int position);
}
RecyclerView.OnScrollListener
有2個核心回調
- void onScrolled(RecyclerView recyclerView, int dx, int dy)
每次RecyclerView滑動的時候都會觸發這個函數,注意,如果已經滑動到底部,那么這個函數就不會再被觸發。 - void onScrollStateChanged(RecyclerView recyclerView, int newState)
每次滑動過程,會觸發3次這個函數,先后順序分別是- SCROLL_STATE_DRAGGING 手指接觸屏幕
- SCROLL_STATE_SETTLING 手指離開屏幕
- SCROLL_STATE_IDLE RecyclerView停止滑動
private RecyclerView.OnScrollListener onScrollListener = new OnScrollListener() {
/**
* 手指滑動過程,recyclerView是否發生滑動
*/
private boolean hasScrolled;
/**
* 是否已經初始化
*/
private boolean inited;
/**
* 數據是否超出屏幕,如果不超出屏幕,那么無論loadMoreEnable是true或者false,都不能加載更多。
*/
private boolean dataOutOfScreen;
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
// recyclerView 停止滑動時
LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
// 只要最后一個item可見,即可開始加載。
boolean scrollToEnd = llm.findLastVisibleItemPosition() == llm.getItemCount() - 1;
if ((dataOutOfScreen ||hasScrolled) && scrollToEnd && loadMoreEnable) {
// 開始加載,并設置只能同時存在一次加載
if (loadMoreListener != null && !isLoading) {
isLoading = true;
loadMoreType = TYPE_LOADMORE_LOADING;
adapterWrapper.notifyDataSetChanged();
loadMoreListener.onLoadMore();
}
}
} else if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
// 手指剛剛觸摸屏幕開始滑動時
hasScrolled = false;
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
hasScrolled = true;
if (!inited) {
init(recyclerView);
}
}
/**
* 如果數據沒有超出屏幕,那么,不會發生加載更多的事件
* @param recyclerView
*/
private void init(RecyclerView recyclerView) {
inited = true;
LinearLayoutManager llm = (LinearLayoutManager) recyclerView.getLayoutManager();
boolean scrollToEnd = llm.findLastCompletelyVisibleItemPosition() == llm.getItemCount() - 1;
if (!scrollToEnd) {
dataOutOfScreen = true;
}
}
};
在setAdapter中初始化AdapterWrapper和RecyclerView.OnScrollListener
public void setAdapter(Adapter adapter) {
mAdapter = adapter;
if (mAdapter == null) {
throw new IllegalArgumentException("adapter不能為null");
}
adapterWrapper = new AdapterWrapper();
super.setAdapter(adapterWrapper);
removeOnScrollListener(onScrollListener);
addOnScrollListener(onScrollListener);
}
注意,由于設置的Adapter被包裝了一層,所以類似notifyDataSetChanged()這樣的接口將沒有效果。