分页是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()这样的接口将没有效果。