继承自SwipeRefreshLayout,实现加载更多
更加强大的实现方式,请看这里
android官方提供的 SwipeRefreshLayout 非常美观实用,但是只有下拉刷新功能,我们项目中一般都是下拉刷新和上拉加载更多同时使用的。
本文将介绍一套工具集,继承自官方 SwipeRefreshLayout, 实现上拉加载更多,并且兼容ListView 和 RecyclerView 以及 RecyclerView 多列网格样式
ListView 和 RecyclerView效果一致,如下:

RecyclerView 多列网格样式如下:

首先列出核心类:
import android.content.Context;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import com.zhangxq.loadrefreshlayout.R;
/**
* Created by zhangxiaoqi on 2019/4/12.
*/
public abstract class RefreshLayout extends SwipeRefreshLayout {
private int mTouchSlop;
private int mYDown;
private int mLastY;
private float mPrevX;
protected boolean isAutoLoad = true;
protected View footView;
private boolean isLoading;
private boolean isLoadEnable;
private OnLoadListener mOnLoadListener;
public RefreshLayout(Context context) {
this(context, null);
}
public RefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
footView = LayoutInflater.from(context).inflate(R.layout.view_list_footer, null);
}
/**
* 解决SwipeRefreshLayout包含ViewPager时的滑动事件冲突
*
* @param event
* @return
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPrevX = MotionEvent.obtain(event).getX();
break;
case MotionEvent.ACTION_MOVE:
final float eventX = event.getX();
float xDiff = Math.abs(eventX - mPrevX);
if (xDiff > mTouchSlop) {
return false;
}
}
return super.onInterceptTouchEvent(event);
}
/**
* 当手指上滑,离开屏幕时加载更多
*
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mYDown = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
mLastY = (int) event.getRawY();
loadData();
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
/**
* 是否是上拉操作
*
* @return
*/
protected boolean isPullUp() {
return mYDown > mLastY;
}
/**
* 加载更多数据
*/
protected void loadData() {
if (isReachBottom() && !isLoading && isPullUp() && isLoadEnable) {
setLoading(true);
if (mOnLoadListener != null) {
mOnLoadListener.onLoad();
}
}
}
/**
* @param loading
*/
public void setLoading(boolean loading) {
isLoading = loading;
if (isLoading) {
showLoadView(true);
} else {
showLoadView(false);
mYDown = 0;
mLastY = 0;
}
}
/**
* 是否滚动到了底部
*
* @return
*/
protected abstract boolean isReachBottom();
/**
* 控制加载更多view的显示可隐藏
*
* @param isShow
*/
protected abstract void showLoadView(boolean isShow);
/**
* 设置自动加载更多,默认开启
*
* @param isAuto
*/
public void setAutoLoad(boolean isAuto) {
isAutoLoad = isAuto;
}
/**
* 设置是否可以上拉加载更多
*
* @param enable
*/
public void setLoadEnable(boolean enable) {
this.isLoadEnable = enable;
}
/**
* 设置加载更多监听器,同时默认开启加载更多开关
*
* @param loadListener
*/
public void setOnLoadListener(OnLoadListener loadListener) {
mOnLoadListener = loadListener;
setLoadEnable(true);
}
}
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import com.zhangxq.loadrefreshlayout.base.OnScrolllistener;
import com.zhangxq.loadrefreshlayout.base.RefreshLayout;
public class ListRefreshLayout extends RefreshLayout {
private ListView mListView;
private OnScrolllistener onScrolllistener;
/**
* @param context
*/
public ListRefreshLayout(Context context) {
this(context, null);
}
public ListRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mListView == null) {
findListView(this);
}
}
private void findListView(View view) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
if (viewGroup.getChildCount() > 0) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View viewItem = viewGroup.getChildAt(i);
if (viewItem instanceof ListView) {
mListView = (ListView) viewItem;
mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
if (onScrolllistener != null) {
onScrolllistener.onScrollStateChanged(absListView, i);
}
}
@Override
public void onScroll(AbsListView absListView, int i, int i1, int i2) {
if (isAutoLoad) {
loadData();
}
if (onScrolllistener != null) {
onScrolllistener.onScroll(absListView, i, i1, i2);
}
}
});
return;
} else {
findListView(viewItem);
}
}
}
}
}
/**
* 判断是否到了最底部
*/
@Override
protected boolean isReachBottom() {
if (mListView == null) {
return false;
}
int position = mListView.getLastVisiblePosition();
int count = 0;
if (mListView.getAdapter() != null) {
count = mListView.getAdapter().getCount();
}
return position == count - 1;
}
@Override
protected void showLoadView(boolean isShow) {
if (mListView == null) return;
if (isShow) {
mListView.addFooterView(footView);
} else {
mListView.removeFooterView(footView);
}
}
public void setCrollListener(OnScrolllistener listener) {
this.onScrolllistener = listener;
}
}
import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import com.zhangxq.loadrefreshlayout.base.RefreshLayout;
public class RecyclerRefreshLayout extends RefreshLayout {
private RecyclerView mRecyclerView;
/**
* @param context
*/
public RecyclerRefreshLayout(Context context) {
this(context, null);
}
public RecyclerRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mRecyclerView == null) {
findListView(this);
}
}
private void findListView(View view) {
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) view;
if (viewGroup.getChildCount() > 0) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
View viewItem = viewGroup.getChildAt(i);
if (viewItem instanceof RecyclerView) {
mRecyclerView = (RecyclerView) viewItem;
if (mRecyclerView.getAdapter() != null && mRecyclerView.getAdapter() instanceof LoadRecyclerAdapter) {
((LoadRecyclerAdapter) mRecyclerView.getAdapter()).setFootView(footView);
}
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (isAutoLoad) {
loadData();
}
}
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
});
return;
} else {
findListView(viewItem);
}
}
}
}
}
/**
* 判断是否到了最底部
*/
@Override
protected boolean isReachBottom() {
if (mRecyclerView == null) {
return false;
} else {
LinearLayoutManager lm = (LinearLayoutManager) mRecyclerView.getLayoutManager();
int position = lm.findLastVisibleItemPosition();
int count = lm.getItemCount();
return position > count - 2;
}
}
@Override
protected void showLoadView(boolean isShow) {
if (mRecyclerView != null && mRecyclerView.getAdapter() != null && mRecyclerView.getAdapter() instanceof LoadRecyclerAdapter) {
LoadRecyclerAdapter adapter = (LoadRecyclerAdapter) mRecyclerView.getAdapter();
if (isShow) {
adapter.showFootView(true);
} else {
adapter.showFootView(false);
}
}
}
}
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by zhangxiaoqi on 2019/4/3.
*/
public abstract class LoadRecyclerAdapter extends RecyclerView.Adapter {
private View footView;
private int footerCount;
private int dataSize;
private Handler handler = new Handler();
void showFootView(boolean isShow) {
if (isShow) {
footerCount = 1;
} else {
footerCount = 0;
}
handler.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
public void setDataSize(int dataSize) {
this.dataSize = dataSize;
handler.post(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
void setFootView(View footView) {
this.footView = footView;
notifyDataSetChanged();
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == 1) {
return onCreateItemViewHolder();
} else {
return new MyFooterViewHolder(footView);
}
}
@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) {
return getItemViewType(position) == 1 ? 1 : gridManager.getSpanCount();
}
});
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (!(holder instanceof LoadRecyclerAdapter.MyFooterViewHolder)) {
onBindItemViewHolder(holder, position);
}
}
public abstract RecyclerView.ViewHolder onCreateItemViewHolder();
public abstract void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position);
@Override
public int getItemCount() {
return dataSize + footerCount;
}
@Override
public int getItemViewType(int position) {
if (position == dataSize) {
return -1;
} else {
return 1;
}
}
class MyFooterViewHolder extends RecyclerView.ViewHolder {
MyFooterViewHolder(View itemView) {
super(itemView);
}
}
}
使用方式
refreshLayout.setOnLoadListener(this);
一般在onCreate方法中调用。
接口介绍
setOnLoadListener:设置加载更多监听器。
setLoadEnable:设置是否可以加载更多,设置加载更多监听器之后会默认开启,一般当加载完全部数据时关闭,下拉刷新后开启。
setAutoLoad:设置列表滚动到底部时是否自动加载更多,默认开启。
setLoading:用于数据加载完成后关闭加载动画。
setCrollListener(仅限ListRefreshLayout):由于需要监听列表滚动,所以ListRefreshLayout占用了滚动监听器,用户可以在这里设置监听器,接收由ListRefreshLayout监听并且回调出来的滚动事件。
代码简介
核心类分为三个,基类RefreshLayout,两个子类ListRefreshLayout,RecyclerRefreshLayout。
ListRefreshLayout用于实现ListView的加载更多。
RecyclerRefreshLayout和LoadRecyclerAdapter配合,用于实现RecyclerView(包含网格形式)的加载更多。
由于ListView和RecyclerView设置滚动监听方式,判断是否滚动底部方式以及设置加载更多动画的方式不一样,所以,RefreshLayout通过虚方法将这些任务交给两个子类分别实现,RefreshLayout实现了核心的加载更多监听设置以及回调,是否加载更多开关的设置,上滑手势的判断。
LoadRecyclerAdapter使用方法
RecyclerRefreshLayout和LoadRecyclerAdapter需要配合使用,RecyclerView必须设置一个继承自LoadRecyclerAdapter的适配器才能正常使用加载更多功能。
LoadRecyclerAdapter使用方法,实现两个虚方法:
onCreateItemViewHolder:使用方式同onCreateViewHolder。
onBindItemViewHolder:使用方式同onBindViewHolder。
设置数据个数:
setDataSize,当数据发生改变,及时设置数据个数。
目前来看,可以满足大部分需求。
加载更多的判断方式
加载更多的判断条件有两个,一个是手指向上滑动,一个是列表滚动到底部,当两个条件都达到时,判断用户是否开启了加载更多选项,如果开启,则触发加载更多回调,并且显示加载更多动画。
相关代码如下:
/**
* 当手指上滑,离开屏幕时加载更多
*
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mYDown = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
mLastY = (int) event.getRawY();
loadData();
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
/**
* 加载更多数据
*/
protected void loadData() {
if (isReachBottom() && !isLoading && isPullUp() && isLoadEnable) {
setLoading(true);
if (mOnLoadListener != null) {
mOnLoadListener.onLoad();
}
}
}
isPullUp() 方法通过 mYDown 和 mLastY 两个参数判断用户手指是否向上滑动。
isReachBottom() 由两个子类实现,判断列表是否滚动到底部。
isLoadEnable,由用户通过setLoadEnable(boolean enable)设置。
加载更多动画设置方式
- ListView
在适当的时机添加footerView和去掉footerView,footView里包含加载更多动画。@Override protected void showLoadView(boolean isShow) { if (mListView == null) return; if (isShow) { mListView.addFooterView(footView); } else { mListView.removeFooterView(footView); } }
- RecyclerView
RecyclerView没有footView的概念,给RecyclerView添加footView其实就是增加了一个特殊的item,所以需要特定的适配器LoadRecyclerAdapter来实现@Override protected void showLoadView(boolean isShow) { if (mRecyclerView != null && mRecyclerView.getAdapter() != null && mRecyclerView.getAdapter() instanceof LoadRecyclerAdapter) { LoadRecyclerAdapter adapter = (LoadRecyclerAdapter) mRecyclerView.getAdapter(); if (isShow) { adapter.showFootView(true); } else { adapter.showFootView(false); } } }
showFootView其实就是修改了footerCount的值,然后通过getItemCount给列表增加或者去掉一个item名额,通过getItemViewType来判断item类型,最后在onCreateViewHolder里,在合适的时机返回并显示footView,这里利用了RecyclerView可以适配不同类型item的特性,来实现显示和隐藏加载更多动画的功能。public void showFootView(boolean isShow) { if (isShow) { footerCount = 1; } else { footerCount = 0; } handler.post(new Runnable() { @Override public void run() { notifyDataSetChanged(); } }); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == 1) { return onCreateItemViewHolder(); } else { return new MyFooterViewHolder(footView); } } @Override public int getItemCount() { return dataSize + footerCount; } @Override public int getItemViewType(int position) { if (position == dataSize) { return -1; } else { return 1; } }
one more
改工具集无法实现GridView加载更多功能,如果想要实现网格型列表,使用RecyclerView设置GridLayoutManager即可。
适配RecyclerView多列列表的代码在LoadRecyclerAdapter的onAttachedToRecyclerView方法里,感兴趣的可以看看。
github源码地址
源码使用方式:直接copy loadrefreshlayout模块里的6个类或者接口到自己的项目即可,或者直接导入loadrefreshlayout模块。