在最近的项目中,为了提高用户的体验,需要实现ListView在滑动到底部的时候进行数据的自动加载,当看到这个需求的时候,我的第一个想法是ListView不是有HeadView和FooterView么,就可以直接拿来用了,最终也的确是用的这个方法,但是在实现的过程中,遇到了很多坑。
首先,先简单写下ListView的FooterView,就是一个简单的一个进度条加上一个文本提示语句。
xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/litview_footview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal"
>
android:id="@+id/listview_footview_progressBar"
style="@android:style/Widget.ProgressBar.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
android:id="@+id/listview_footview_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:layout_gravity="center"
android:layout_toRightOf="@+id/listview_footview_progressBar"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:paddingTop="10dp"
android:text="正在加载" />
然后我们在初始化适配器和ListView本身的时候来将这个footerView加入到ListView中去。有人说需要在绑定适配器之前将FooterView和HeaderView加到ListView中去,然而我试了一下,感觉并没有什么关系的,不知道还有什么猫腻。如果谁知道还请告诉我哦,哈哈。
private void addListViewFooterView() {
footer = getActivity().getLayoutInflater().inflate(R.layout.listview_footerview, null);
footProgressBar = (ProgressBar) footer.findViewById(R.id.listview_footview_progressBar);
footTextView = (TextView) footer.findViewById(R.id.listview_footview_textview);
listviewMine.addFooterView(footer);
footTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (footTextView.getText().equals(R.string.load_error)) { //加载数据
getMoreData();
footTextView.setText(R.string.loading);
footProgressBar.setVisibility(View.VISIBLE);
}
}
});
}
在上面的代码中,我将footerView引入进来,然后将里面的控件也取出来,因为后面要对两个控件进行操作,最后将布局设置成ListView的footerView,最后对TextView进行事件的监听,因为在加载错误的时候用户可以点击重新加载数据,当然这个不是重点。
然后现在需要对ListView的滑动事件进行监听了,当滚到最后一条数据的时候就要自动加载数据了。
listviewMine.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 当不滚动时
if (scrollState == SCROLL_STATE_IDLE) {
//判断是否滚动到底部
if (!isLoading && view.getLastVisiblePosition() == view.getCount() - 1) {
isLoading = true;
getMoreData(); Log.d("Rollr_Mine", "请求数据...");
return;
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// if (visibleItemCount == totalItemCount) {
// //此时说明当前ListView所有的条目比较少,不足一屏
// litviewFootview.setVisibility(View.GONE);
// } else if (!isLoading&&(firstVisibleItem + visibleItemCount >= totalItemCount)
// &&totalItemCount!=0) {
// //当第一个可见的条目位置加上当前也所有可见的条目数 等于 ListView当前总的条目数时,就说明已经滑动到了底部,这时候就要去显示FooterView。
// isLoading = true;
// getMoreData(BaseApp.getInstance().getUserInfo().getUserId(), mCursor);
// Log.d("Rollr_Mine","请求数据...");
// litviewFootview.setVisibility(View.VISIBLE);
// }
}
});
}
上面的代码中就有一个大坑,首先,我们要选择到底是在哪儿来进行操作,有两个方法,一个是
onScrollStateChanged()方法,这个方法是滑动停止的时候才会触发,还有一个方法是onScroll(),这个方法会一直触发,从上面的注释可以看到,当时我是在这填坑了的,最后我还是决定用上面的方法,就是那个滚动停止的时候触发的方法,我们来看一下,假如我们使用下面的方法来实现,这样当滑动到底部的时候,当快要滑动到底部的时候,加载数据的代码会执行,此时,isLoading为true,然后当数据量很少的时候,网络很快的时候,加载数据的代码会很快得到数据,将isLoading的值设成false,此时滑动还没有结束,又满足了所有的条件,又会继续请求数据,所以就会造成在一瞬间,这个模块请求了十几次接口的数据,很明显,这样是不对的,不管是对客户端还是服务器都是不可原谅的,当然有些人会说,只要判断条件再给明确一点就可以避免这个错误,那是当然,我只是觉得这样不好而已。
然后选择好方法后,我们开始前进了,当滑动到底部,看见了footerView后,停止滑动的时候,满足条件去请求数据了,然后下面是请求数据的代码。
private void getMoreData() {
getFeedsService().getTopicByUserId().enqueue(new Callback<ResultModel<List<TopicModel>>>() {
@Override
public void onResponse(Response<ResultModel<List<TopicModel>>> response) {
if (response.isSuccess()) {
if (response.body().getData() != null) {
if (response.body().getData().isEmpty()) {
footTextView.setText(R.string.no_more_data);
footProgressBar.setVisibility(View.GONE);
} else {
list.addAll(response.body().getData());
isLoading = false;
}
}
adapter.notifyDataSetChanged();
} else {
try {
footTextView.setText(R.string.load_error);
footProgressBar.setVisibility(View.GONE);
Toast.makeText(getActivity(), StringUtils.getJsonString(response.errorBody().string(), "message"), Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Throwable t) {
footTextView.setText(R.string.load_error);
footProgressBar.setVisibility(View.GONE);
}
});
}
当我们请求到数据的时候,我们会先去是否能够取到数据,如果不能取到数据,做对应的处理,并且将提示放在footerView的TextView里面,比如我的就是如果出错,就在footerView的TextView上显示,加载出错,重新加载,并且要将ProgressBar隐藏掉,然后加载成功的话也分情况,如果在加载成功的前提下,有数据的话不做任何处理,如果返回的数据为空,代表已经没有更多数据加载了。
在之前的时候我还在考虑在加载完毕的时候将footerView隐藏掉,等到再滑到底部的时候再显示出来,其实这个想着觉得是挺好的,做起来体验并不好,为什么?一来,布局的隐藏是个蛋疼的事情,不过也有解决办法,一来你可以想到removeFooterView,等到需要的时候,再addFooterView,不过你自己使用的时候才知道并没有卵用,如果remove掉了,你再加是加不上的,然后你可能会想到,我通过setVisibility(View.GONE);来隐藏,需要的时候再显示,你试过就会知道这样做会有一个白色的空白,超级难看,当然,这个我也可以帮你解决,用下面的方式你可以做到。而且还没有后遗症,好,你觉得一切都可以了,试一试呗。
//显示
footerView.setVisibility(View.VISIBLE);
footerView.setPadding(0, 0, 0, 0);
//隐藏
footerView.setVisibility(View.GONE);
footerView.setPadding(0, -footerView.getHeight(), 0, 0);
当试过后你就会发现,当你滑动到底部的时候,如果滑动操作还没有结束,不会执行操作,意思就是那个显示正在加载的那个footerView不会显示,当稳定后,方法触发,那个footerView才会显示,也就是说,你滑动啊滑动,滑到底部的时候啥都没有,然后你松开手指,突然蹦出一个正在加载的View,反正是我就骂娘了,吓死宝宝了。
说了这么多,就是想说,其实那个footerView没必要隐藏和显示,就一直放那,滑动底部的时候就能看到,正在加载,加载成功了,数据取到了,刷新适配器,那个footerView就会被挤走,你就看不到了,也不会那么突兀。还可以作为一个指示器,也挺好的。
最后总结一下用法:
第一步、书写footerView,自定义的
第二步、绑定适配器并将footerV添加到ListView中去
第三步、给ListView添加滚动时间监听 就按上面的方法,当看到最后一项并且不再滑动的时候请求数据,当请求数据出错的时候,做不同的处理,并且改变footerView中TextView的文字显示,并根据情况显示还是隐藏ProgressBar,这些看情况吧。
好了,也不知道说清楚没有,这个也没必要给连接了,呵呵。
最后吐槽一下,现在的博客抄袭真的是,日了狗了,原创越来越少了,有时候搜点东西吧,一搜好几页的帖子,一打开,发现内容都一样,唉,真的,抄人家的有什么用啊。
好了,不早了,宝宝休息了。
-----------更新------------
我也是很讨厌那种光说不练的人,说一大堆,还不如代码来的实在,所以我抽空写了个demo。。不过万事看需求。。这个不一定适合你,你需要自己缝缝补补才会更适合你。。
github地址:https://github.com/MZCretin/AutoRefreshListView
----------------------------
如果您觉得不错,请打赏我一杯咖啡的钱!