Android實現(xiàn)上拉加載更多ListView(PulmListView)
思路
今天帶大家實現(xiàn)一個上拉加載更多的ListView.GitHub傳送門:PulmListView, 歡迎大家fork&&star.
先帶大家理一下思路, 如果我們要實現(xiàn)一個上拉加載更多的ListView, 我們需要實現(xiàn)的功能包括:
1.一個自定義的ListView, 并且該ListView能夠判斷當(dāng)前是否已經(jīng)處于最底部.
2.一個自定義的FooterView, 用于在ListView加載更多的過程中進(jìn)行UI展示.
3.關(guān)聯(lián)FooterView和ListView, 包括加載時機判斷、FooterView的顯示和隱藏.
4.提供一個加載更多的接口, 便于回調(diào)用戶真正加載更多的功能實現(xiàn).
5.提供一個加載更多結(jié)束的回調(diào)方法, 用于添加用戶的最新數(shù)據(jù)并更新相關(guān)狀態(tài)標(biāo)記和UI顯示.
針對上面的5個功能, 我們挨個分析對應(yīng)的實現(xiàn)方法.
功能1(自定義ListView)
我們可以通過繼承ListView, 實現(xiàn)一個自定義的PulmListView.
public class PulmListView extends ListView {
public PulmListView(Context context) {
this(context, null);
}
public PulmListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PulmListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 初始化
init();
}
}
只是實現(xiàn)ListView的三個構(gòu)造函數(shù)還不夠, 我們需要ListView能夠判斷當(dāng)前的ListView是否滑動到最后一個元素.
判斷是否滑動到最后一個元素, 我們可以通過為ListView設(shè)置OnScrollListener來實現(xiàn).代碼如下:
private void init() {
super.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 調(diào)用用戶設(shè)置的OnScrollListener
if (mUserOnScrollListener != null) {
mUserOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 調(diào)用用戶設(shè)置的OnScrollListener
if (mUserOnScrollListener != null) {
mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
// firstVisibleItem是當(dāng)前屏幕能顯示的第一個元素的位置
// visibleItemCount是當(dāng)前屏幕能顯示的元素的個數(shù)
// totalItemCount是ListView包含的元素總數(shù)
int lastVisibleItem = firstVisibleItem + visibleItemCount;
if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) {
if (mOnPullUpLoadMoreListener != null) {
mIsLoading = true;
mOnPullUpLoadMoreListener.onPullUpLoadMore();
}
}
}
});
}
從代碼注釋可以知道, 通過(firstVisibleItem + visibleItemCount)可以獲取當(dāng)前屏幕已經(jīng)展示的元素個數(shù), 如果已經(jīng)展示的元素個數(shù)等于ListView的元素總數(shù), 則此時可以認(rèn)為ListView已經(jīng)滑動到底部.
功能2(自定義的FooterView)
這里我們可以實現(xiàn)一個比較簡單的FooterView, 即加載更多的UI布局.例如我們可以展示一個ProgressBar和一行文字, 具體代碼如下:
/**
* 加載更多的View布局,可自定義.
*/
public class LoadMoreView extends LinearLayout {
public LoadMoreView(Context context) {
this(context, null);
}
public LoadMoreView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LoadMoreView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
LayoutInflater.from(getContext()).inflate(R.layout.lv_load_more, this);
}
}
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/id_load_more_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:layout_margin="@dimen/loading_view_margin_layout">
<ProgressBar
android:id="@+id/id_loading_progressbar"
android:layout_width="@dimen/loading_view_progress_size"
android:layout_height="@dimen/loading_view_progress_size"
android:indeterminate="true"
style="?android:progressBarStyleSmall"/>
<TextView
android:id="@+id/id_loading_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/page_loading"/>
</LinearLayout>
功能3(關(guān)聯(lián)ListView和FooterView)
第一,我們需要在ListView中通過一個變量保存FooterView, 并且在構(gòu)造函數(shù)中將其實例化.
private View mLoadMoreView;
private void init() {
mLoadMoreView = new LoadMoreView(getContext());
}
第二,我們需要控制FooterView的顯示和隱藏.考慮一下FooterView的顯示和隱藏的時機:
•顯示的時機: ListView處于最底部并且當(dāng)前還有更多的數(shù)據(jù)需要加載.
•隱藏的時機: ListView結(jié)束完加載更多的操作.
為了判斷當(dāng)前是否還有數(shù)據(jù)需要加載, 因此我們需要定義一個boolean變量mIsPageFinished, 表示數(shù)據(jù)加載是否結(jié)束.
為了保證同一時間只進(jìn)行一次數(shù)據(jù)加載過程, 因此我們還需要定義一個boolean變量mIsLoading, 表示當(dāng)前是否已經(jīng)處于數(shù)據(jù)加載狀態(tài).
明確了FooterView的顯示和隱藏時機, 也有了控制狀態(tài)的變量, 代碼也就比較容易實現(xiàn)了.
顯示時機:
private void init() {
mIsLoading = false; // 初始化時沒處于加載狀態(tài)
mIsPageFinished = false; // 初始化時默認(rèn)還有更多數(shù)據(jù)需要加載
mLoadMoreView = new LoadMoreView(getContext()); // 實例化FooterView
super.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// 調(diào)用用戶設(shè)置的OnScrollListener
if (mUserOnScrollListener != null) {
mUserOnScrollListener.onScrollStateChanged(view, scrollState);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 調(diào)用用戶設(shè)置的OnScrollListener
if (mUserOnScrollListener != null) {
mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
int lastVisibleItem = firstVisibleItem + visibleItemCount;
// 當(dāng)處于ListView尾部且有更多數(shù)據(jù)需要加載且當(dāng)前沒有加載程序再進(jìn)行中時, 執(zhí)行加載更多操作
if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) {
if (mOnPullUpLoadMoreListener != null) {
mIsLoading = true; // 將加載更多進(jìn)行時狀態(tài)設(shè)置為true
showLoadMoreView(); // 顯示加載更多布局
mOnPullUpLoadMoreListener.onPullUpLoadMore(); // 調(diào)用用戶設(shè)置的加載更多回調(diào)接口
}
}
}
});
}
private void showLoadMoreView() {
// 這里將加載更多的根布局id設(shè)置為id_load_more_layout, 便于用戶自定制加載更多布局.
if (findViewById(R.id.id_load_more_layout) == null) {
addFooterView(mLoadMoreView);
}
}
隱藏時機:
/**
* 加載更多結(jié)束后ListView回調(diào)方法.
*
* @param isPageFinished 分頁是否結(jié)束
* @param newItems 分頁加載的數(shù)據(jù)
* @param isFirstLoad 是否第一次加載數(shù)據(jù)(用于配置下拉刷新框架使用, 避免出現(xiàn)頁面閃現(xiàn))
*/
public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) {
mIsLoading = false; // 標(biāo)記當(dāng)前已經(jīng)沒有加載更多的程序在執(zhí)行
setIsPageFinished(isPageFinished); // 設(shè)置分頁是否結(jié)束標(biāo)志并移除FooterView
}
private void setIsPageFinished(boolean isPageFinished) {
mIsPageFinished = isPageFinished;
removeFooterView(mLoadMoreView);
}
功能4(上拉加載更多實現(xiàn)的回調(diào)接口)
這個比較簡單, 我們定義一個interface, 便于回調(diào)用戶真正的加載更多的實現(xiàn)方法.
/**
* 上拉加載更多的回調(diào)接口
*/
public interface OnPullUpLoadMoreListener {
void onPullUpLoadMore();
}
private OnPullUpLoadMoreListener mOnPullUpLoadMoreListener;
/**
* 設(shè)置上拉加載更多的回調(diào)接口.
* @param l 上拉加載更多的回調(diào)接口
*/
public void setOnPullUpLoadMoreListener(OnPullUpLoadMoreListener l) {
this.mOnPullUpLoadMoreListener = l;
}
功能5(加載更多的結(jié)束回調(diào))
為了在PulmListView中維護(hù)數(shù)據(jù)集合, 必須自定義一個Adapter, 在Adapter中使用List存儲數(shù)據(jù)集合, 并提交增刪的方法.
自定義的Adapter:
/**
* 抽象的Adapter.
*/
public abstract class PulmBaseAdapter<T> extends BaseAdapter {
protected List<T> items;
public PulmBaseAdapter() {
this.items = new ArrayList<>();
}
public PulmBaseAdapter(List<T> items) {
this.items = items;
}
public void addMoreItems(List<T> newItems, boolean isFirstLoad) {
if (isFirstLoad) {
this.items.clear();
}
this.items.addAll(newItems);
notifyDataSetChanged();
}
public void removeAllItems() {
this.items.clear();
notifyDataSetChanged();
}
}
為什么在addMoreItems方法中要增加一個isFirstLoad變量呢?
是因為上拉加載更多通常要配合下拉刷新使用.而下拉刷新的過程中會牽扯到ListView的數(shù)據(jù)集合clear然后再addAll.如果沒有isFirstLoad參數(shù), 那用戶下拉刷新去更新ListView的數(shù)據(jù)集合就必須分為兩步:
1.removeAllItems并進(jìn)行notifyDataSetChanged.
2.addMoreItems并進(jìn)行notifyDataSetChanged.
同一時間連續(xù)兩次notifyDataSetChanged會導(dǎo)致屏幕閃屏, 因此這里提交了一個isFirstLoad方法.當(dāng)是第一次加載數(shù)據(jù)時, 會先clear掉所有的數(shù)據(jù), 然后再addAll, 最后再notify.
有了自定義的adapter, 就可以寫加載更多結(jié)束的回調(diào)函數(shù)了:
/**
* 加載更多結(jié)束后ListView回調(diào)方法.
*
* @param isPageFinished 分頁是否結(jié)束
* @param newItems 分頁加載的數(shù)據(jù)
* @param isFirstLoad 是否第一次加載數(shù)據(jù)(用于配置下拉刷新框架使用, 避免出現(xiàn)頁面閃現(xiàn))
*/
public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) {
mIsLoading = false;
setIsPageFinished(isPageFinished);
// 添加更新后的數(shù)據(jù)
if (newItems != null && newItems.size() > 0) {
PulmBaseAdapter adapter = (PulmBaseAdapter) ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();
adapter.addMoreItems(newItems, isFirstLoad);
}
}
這里需要注意, 當(dāng)添加了FooterView或者HeaderView之后, 我們無法通過listview.getAdapter拿到我們自定義的adapter, 必須按照如下步驟:
參考
1.PagingListView
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android實現(xiàn)listview滑動時漸隱漸現(xiàn)頂部欄實例代碼
- Android實現(xiàn)有視差效果的ListView
- Android ListView自動顯示隱藏布局的實現(xiàn)方法
- Android UI控件ExpandableListView基本用法詳解
- Android中ListView綁定CheckBox實現(xiàn)全選增加和刪除功能(DEMO)
- Android之ListView分頁加載數(shù)據(jù)功能實現(xiàn)代碼
- Android實現(xiàn)Listview異步加載網(wǎng)絡(luò)圖片并動態(tài)更新的方法
- Android ListView的OnItemClickListener詳解
- Android實現(xiàn)帶有邊框的ListView和item的方法
- Android ListView下拉刷新上拉自動加載更多DEMO示例
- Android ListView position詳解及實例代碼
相關(guān)文章
Android 使用RecycleView列表實現(xiàn)加載更多的示例代碼
這篇文章主要介紹了Android 使用RecycleView列表實現(xiàn)加載更多的示例代碼,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-05-05
kotlin中EditText賦值Type mismatch方式
這篇文章主要介紹了kotlin中EditText賦值Type mismatch方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
RecyclerView使用payload實現(xiàn)局部刷新
這篇文章主要為大家詳細(xì)介紹了RecyclerView使用payload實現(xiàn)局部刷新,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-10-10

