前言:
基本上所以的移动端应用都有Listview(当然RecyclerView也一样),那必不可少的都会嵌入一个上拉加载下拉刷新的功能。这样能大大的减少用户的流量消耗,同样对于用户也有更好的用户体验。说到这个功能,那必不可少的会涉及到
SwipeRefreshLayout,这是由Google给我们封装好的一个sdk,他支持我们listview的下拉刷新功能。当然也可以自己自定义一个下拉刷新的view,这样能根据自己的项目来设计,这样更有个性化吧。不过我这仅仅使用这个SwipeRefreshLayout来构建。当然仅仅有它还是不够的,还需要对它进行一层封装,增加一个上拉加载的功能。SwipeRefreshLayout这里就不详细介绍了,网上有很多介绍资料。
先谈下SwipeRefreshLayout的封装-增加上拉加载的功能
1.我们先要有个footerview,它的作用就是当能上拉加载的时候显示的。大部分app都是一句提示,上拉加载更多
2.只有listview滑动到底部的时候才会触发上拉加载的功能,所以我们要知道listview是否滑动到底部,同样的它也决定我们footerview显示的时机。
3.通过滑动的距离来判断,我们是否在进行上拉操作。
4.通过标识来判断是否正在加载数据,避免重复上拉而导致重复调用接口。
好了,大致思路就这样,接下来就对SwipeRefreshLayout来进行封装吧,代码中都有注释,我就不细说了。
public class RefreshLayout extends SwipeRefreshLayout {
//最小滑动距离,超过则判断是否是上拉操作
private final int mTouchSlop;
private ListView mListView;
//提供获取数据接口
private OnLoadListener mOnLoadListener;
//用来判断是否是上拉操作
private float firstTouchY;
private float lastTouchY;
//Listview是否正在加载数据
private boolean isLoading = false;
public RefreshLayout(Context context) {
this(context, null);
}
public RefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
//set the child view of RefreshLayout,ListView
public void setChildView(ListView mListView) {
this.mListView = mListView;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
firstTouchY = event.getRawY();
break;
case MotionEvent.ACTION_UP:
lastTouchY = event.getRawY();
//是否能加载数据
if (canLoadMore()) {
loadData();
}
break;
default:
break;
}
return super.dispatchTouchEvent(event);
}
//是否能加载数据
private boolean canLoadMore() {
return isBottom() && !isLoading && isPullingUp();
}
//判断Listview是否滑动到底部
private boolean isBottom() {
if (mListView.getCount() > 0) {
if (mListView.getLastVisiblePosition() == mListView.getAdapter().getCount() - 1 &&
mListView.getChildAt(mListView.getChildCount() - 1).getBottom() <= mListView.getHeight()) {
return true;
}
}
return false;
}
//是否正在上拉
private boolean isPullingUp() {
return (firstTouchY - lastTouchY) >= mTouchSlop;
}
private void loadData() {
if (mOnLoadListener != null) {
setLoading(true);
}
}
public void setLoading(boolean loading) {
if (mListView == null) return;
isLoading = loading;
if (loading) {
if (isRefreshing()) {
setRefreshing(false);
}
//避免listview刷新的时候直接滑动到顶部
mListView.setSelection(mListView.getAdapter().getCount() - 1);
mOnLoadListener.onLoad();
} else {
firstTouchY = 0;
lastTouchY = 0;
}
}
//提供接口,执行获取数据操作
public void setOnLoadListener(OnLoadListener loadListener) {
mOnLoadListener = loadListener;
}
public interface OnLoadListener {
public void onLoad();
}
}
footerview我就没定义在里面了,这里主要是获取footerview显示的时机。
第二部,构建一个utils,用来封装我们的刷新与加载操作,同时可以在其中添加我们的加载动画,footerview的显示时机等。
1.处理具体的上拉加载操作,下拉刷新操作,这里是通过暴露接口的方式提供给个个页面的。
2.获取数据之后的操作,往adapter中设值,以及动画,footerview的显示等。
大致上就这两个点,说的浅显点,我们无非就是要获取数据,然后把数据给显示出来。接下来看下我们utils的代码。
public class RefreshHelper <T>{
//刷新框架
private RefreshLayout refreshLayout;
//正在加载时显示的动画,这个就要自己根据自己项目来定义了,这代码我就不贴出来了
private LoadingLayout loadingLayout;
//获取第几页的数据
private int cuurentPage = 1;
//当前页的条数
private int mCurrentPageCount = 0;
//是否能加载更多
private boolean isLoadMore = true;
//是否正在刷新, 避免接口慢的情况下,同时执行上拉下载和下拉刷新的操作
private boolean isRefresh = false;
//自定义的footerview,这footerview的代码就不贴了,这个自己根据自己项目的ui设计来定义吧
private XListViewFooter footerView;
private Context mContext;
private ListView mListView;
private OnGetListViewDataInterface onGetListViewDataInterface = null;
public interface OnGetListViewDataInterface{
//获取服务端数据接口,用于刷新以及下拉
void getDataList(int currentPage);
//清除adapter中的数据
void clearData();
//往adapter中放数据
void setData();
}
private Handler handler;
public RefreshHelper(RefreshLayout refreshLayout, LoadingLayout loadingLayout, XListViewFooter footerView, Context mContext, ListView mListView, Handler handler, OnGetListViewDataInterface onGetListViewDataInterface){
this.refreshLayout = refreshLayout;
this.loadingLayout = loadingLayout;
this.footerView = footerView;
this.mContext = mContext;
this.onGetListViewDataInterface = onGetListViewDataInterface;
this.mListView = mListView;
this.handler = handler;
init();
}
//初始化refreshlayout,以及加载,刷新操作
private void init() {
refreshLayout.setChildView(mListView);
//直接转成HeaderViewListAdapter,防止强转失败异常
if(mListView.getFooterViewsCount()==0){
mListView.addFooterView(footerView);
}
//下拉刷新动画处理
refreshLayout.setColorSchemeResources(R.color.color_swipeRefreshLayout1,
R.color.color_swipeRefreshLayout2, R.color.color_swipeRefreshLayout3,
R.color.color_swipeRefreshLayout4);
//下拉刷新操作
refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
isRefresh = true;
//避免正在下拉的时候执行上拉操作,其实已经在外层通过 isRefresh这个值来标识了,
refreshLayout.setRefreshing(true);
handler.postDelayed(new Runnable() {
@Override
public void run() {
loadingLayout.setLoadStrat();
refreshLayout.setRefreshing(false);
//每次刷新重新加载次初始数据
cuurentPage = 1;
onGetListViewDataInterface.clearData();
onGetListViewDataInterface.getDataList(cuurentPage);
}
}, 2000);
}
});
//是否加载更多
refreshLayout.setOnLoadListener(new RefreshLayout.OnLoadListener() {
@Override
public void onLoad() {
//如果可以加载更多,并且不是正在获取数据,则调起接口获取数据
if(isLoadMore && !isRefresh){
refreshLayout.setLoading(false);
footerView.loading();
cuurentPage++;
onGetListViewDataInterface.getDataList(cuurentPage);
}else{
refreshLayout.setLoading(false);
}
}
});
//设置动画效果,这个就要根据自己项目来定了,也就是一个加载动画,未加载动画定义一个点击事件,当接口获取失败时,重新调起接口
loadingLayout.setBtnRetry(new View.OnClickListener() {
@Override
public void onClick(View v) {
//检查网络是否连接
if (NetUtil.checkNetIsAccess(mContext)) {
loadingLayout.setLoadStrat();
//这里是初次进入页面调起接口失败重新调起接口时候的操作,
cuurentPage = 1;
onGetListViewDataInterface.getDataList(cuurentPage);
} else {
loadingLayout.setLoadStop(false, null, "请检查网络连接");
}
}
});
}
//从服务端获取到数据之后的,后续操作
public void laterHandle(List<T> mList){
if(mList != null){
mCurrentPageCount = mList.size();
//当前页的Count>0的时候,如果当前页的Count == 20则可以上拉加载,否则不行
if(mCurrentPageCount > 0){
//这里Constant.PAGE_SIZE == 20
if(mCurrentPageCount == Constant.PAGE_SIZE){
isLoadMore = true;
//这里footerview的操作是 显示提示语,上拉下载更多
footerView.normal();
}else{
isLoadMore = false;
mListView.removeFooterView(footerView);
}
onGetListViewDataInterface.setData();
isRefresh = false;
}
//如果当前页码为第一页,且Record为0,则隐藏listview
if(mCurrentPageCount == 0 && cuurentPage == 1){
isLoadMore = false;
mListView.setVisibility(View.GONE);
refreshLayout.setVisibility(View.GONE);
}
//如果当前页不为1,切加载到的Record为0,则已经没有更多的数据了
if(mCurrentPageCount == 0 && cuurentPage>1){
ToastUtil.show(mContext, "没有更多数据了");
isLoadMore = false;
mListView.removeFooterView(footerView);
}
}else {
ToastUtil.show(mContext, "没有更多数据了");
isLoadMore = false;
mListView.setVisibility(View.GONE);
refreshLayout.setVisibility(View.GONE);
}
//停止加载动画
loadingLayout.setLoadStop(true, null, null);
isRefresh = false;
}
public void setCuurentPage(int cuurentPage) {
this.cuurentPage = cuurentPage;
}
public int getCuurentPage() {
return cuurentPage;
}
public OnGetListViewDataInterface getOnGetListViewDataInterface() {
return onGetListViewDataInterface;
}
public void setOnGetListViewDataInterface(OnGetListViewDataInterface onGetListViewDataInterface) {
this.onGetListViewDataInterface = onGetListViewDataInterface;
}
}
具体架构我们已经搭建好了,这里仅仅只是提供一些必要的操作,当然你也可以根据自己要处理的业务来添加内容。那接下来就是如何使用这个了。先贴下布局代码。
由于我这里也没写demo,最近一直在优化项目太忙了,就直接拿我在项目中写的框架来讲解的,所以我就只没整全部的代码来了。
然后就是我们的初始化操作,以及接口的调起还有获取数据之后的操作。
1.初始化操作代码:
2.调起接口操作:
3.获取数据之后的操作,也就是我们handlemessage中的操作
好了,整个流程已经介绍到这里了,多琢磨琢磨代码,你就会发现实现上拉加载下拉刷新其实就这么简单,希望这个框架能帮助到你。值得一提的是,我这为什么没有把这个框架写成单例模式?就是因为避免在不注意的时候可能会出现内存泄漏。所以有时候单例还是要慎用的。不过这个具体还是要根据自己实际情况来吧。