在最近的項目中,爲了提高用戶的體驗,需要實現ListView在滑動到底部的時候進行數據的自動加載,當看到這個需求的時候,我的第一個想法是ListView不是有HeadView和FooterView麼,就可以直接拿來用了,最終也的確是用的這個方法,但是在實現的過程中,遇到了很多坑。
首先,先簡單寫下ListView的FooterView,就是一個簡單的一個進度條加上一個文本提示語句。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
>
<ProgressBar
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" />
<TextView
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="正在加載" />
</LinearLayout>
然後我們在初始化適配器和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
----------------------------
如果您覺得不錯,請打賞我一杯咖啡的錢!