編輯:關於Android編程
思路
今天帶大家實現一個上拉加載更多的ListView.GitHub傳送門:PulmListView, 歡迎大家fork&&star.
先帶大家理一下思路, 如果我們要實現一個上拉加載更多的ListView, 我們需要實現的功能包括:
1.一個自定義的ListView, 並且該ListView能夠判斷當前是否已經處於最底部.
2.一個自定義的FooterView, 用於在ListView加載更多的過程中進行UI展示.
3.關聯FooterView和ListView, 包括加載時機判斷、FooterView的顯示和隱藏.
4.提供一個加載更多的接口, 便於回調用戶真正加載更多的功能實現.
5.提供一個加載更多結束的回調方法, 用於添加用戶的最新數據並更新相關狀態標記和UI顯示.
針對上面的5個功能, 我們挨個分析對應的實現方法.
功能1(自定義ListView)
我們可以通過繼承ListView, 實現一個自定義的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(); } }
只是實現ListView的三個構造函數還不夠, 我們需要ListView能夠判斷當前的ListView是否滑動到最後一個元素.
判斷是否滑動到最後一個元素, 我們可以通過為ListView設置OnScrollListener來實現.代碼如下:
private void init() { super.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 調用用戶設置的OnScrollListener if (mUserOnScrollListener != null) { mUserOnScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // 調用用戶設置的OnScrollListener if (mUserOnScrollListener != null) { mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } // firstVisibleItem是當前屏幕能顯示的第一個元素的位置 // visibleItemCount是當前屏幕能顯示的元素的個數 // totalItemCount是ListView包含的元素總數 int lastVisibleItem = firstVisibleItem + visibleItemCount; if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) { if (mOnPullUpLoadMoreListener != null) { mIsLoading = true; mOnPullUpLoadMoreListener.onPullUpLoadMore(); } } } }); }
從代碼注釋可以知道, 通過(firstVisibleItem + visibleItemCount)可以獲取當前屏幕已經展示的元素個數, 如果已經展示的元素個數等於ListView的元素總數, 則此時可以認為ListView已經滑動到底部.
功能2(自定義的FooterView)
這裡我們可以實現一個比較簡單的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" /> <TextView android:id="@+id/id_loading_label" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/page_loading"/> </LinearLayout>
功能3(關聯ListView和FooterView)
第一,我們需要在ListView中通過一個變量保存FooterView, 並且在構造函數中將其實例化.
private View mLoadMoreView; private void init() { mLoadMoreView = new LoadMoreView(getContext()); }
第二,我們需要控制FooterView的顯示和隱藏.考慮一下FooterView的顯示和隱藏的時機:
•顯示的時機: ListView處於最底部並且當前還有更多的數據需要加載.
•隱藏的時機: ListView結束完加載更多的操作.
為了判斷當前是否還有數據需要加載, 因此我們需要定義一個boolean變量mIsPageFinished, 表示數據加載是否結束.
為了保證同一時間只進行一次數據加載過程, 因此我們還需要定義一個boolean變量mIsLoading, 表示當前是否已經處於數據加載狀態.
明確了FooterView的顯示和隱藏時機, 也有了控制狀態的變量, 代碼也就比較容易實現了.
顯示時機:
private void init() { mIsLoading = false; // 初始化時沒處於加載狀態 mIsPageFinished = false; // 初始化時默認還有更多數據需要加載 mLoadMoreView = new LoadMoreView(getContext()); // 實例化FooterView super.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 調用用戶設置的OnScrollListener if (mUserOnScrollListener != null) { mUserOnScrollListener.onScrollStateChanged(view, scrollState); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // 調用用戶設置的OnScrollListener if (mUserOnScrollListener != null) { mUserOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); } int lastVisibleItem = firstVisibleItem + visibleItemCount; // 當處於ListView尾部且有更多數據需要加載且當前沒有加載程序再進行中時, 執行加載更多操作 if (!mIsLoading && !mIsPageFinished && lastVisibleItem == totalItemCount) { if (mOnPullUpLoadMoreListener != null) { mIsLoading = true; // 將加載更多進行時狀態設置為true showLoadMoreView(); // 顯示加載更多布局 mOnPullUpLoadMoreListener.onPullUpLoadMore(); // 調用用戶設置的加載更多回調接口 } } } }); } private void showLoadMoreView() { // 這裡將加載更多的根布局id設置為id_load_more_layout, 便於用戶自定制加載更多布局. if (findViewById(R.id.id_load_more_layout) == null) { addFooterView(mLoadMoreView); } }
隱藏時機:
/** * 加載更多結束後ListView回調方法. * * @param isPageFinished 分頁是否結束 * @param newItems 分頁加載的數據 * @param isFirstLoad 是否第一次加載數據(用於配置下拉刷新框架使用, 避免出現頁面閃現) */ public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) { mIsLoading = false; // 標記當前已經沒有加載更多的程序在執行 setIsPageFinished(isPageFinished); // 設置分頁是否結束標志並移除FooterView } private void setIsPageFinished(boolean isPageFinished) { mIsPageFinished = isPageFinished; removeFooterView(mLoadMoreView); }
功能4(上拉加載更多實現的回調接口)
這個比較簡單, 我們定義一個interface, 便於回調用戶真正的加載更多的實現方法.
/** * 上拉加載更多的回調接口 */ public interface OnPullUpLoadMoreListener { void onPullUpLoadMore(); } private OnPullUpLoadMoreListener mOnPullUpLoadMoreListener; /** * 設置上拉加載更多的回調接口. * @param l 上拉加載更多的回調接口 */ public void setOnPullUpLoadMoreListener(OnPullUpLoadMoreListener l) { this.mOnPullUpLoadMoreListener = l; }
功能5(加載更多的結束回調)
為了在PulmListView中維護數據集合, 必須自定義一個Adapter, 在Adapter中使用List存儲數據集合, 並提交增刪的方法.
自定義的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的數據集合clear然後再addAll.如果沒有isFirstLoad參數, 那用戶下拉刷新去更新ListView的數據集合就必須分為兩步:
1.removeAllItems並進行notifyDataSetChanged.
2.addMoreItems並進行notifyDataSetChanged.
同一時間連續兩次notifyDataSetChanged會導致屏幕閃屏, 因此這裡提交了一個isFirstLoad方法.當是第一次加載數據時, 會先clear掉所有的數據, 然後再addAll, 最後再notify.
有了自定義的adapter, 就可以寫加載更多結束的回調函數了:
/** * 加載更多結束後ListView回調方法. * * @param isPageFinished 分頁是否結束 * @param newItems 分頁加載的數據 * @param isFirstLoad 是否第一次加載數據(用於配置下拉刷新框架使用, 避免出現頁面閃現) */ public void onFinishLoading(boolean isPageFinished, List<?> newItems, boolean isFirstLoad) { mIsLoading = false; setIsPageFinished(isPageFinished); // 添加更新後的數據 if (newItems != null && newItems.size() > 0) { PulmBaseAdapter adapter = (PulmBaseAdapter) ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter(); adapter.addMoreItems(newItems, isFirstLoad); } }
這裡需要注意, 當添加了FooterView或者HeaderView之後, 我們無法通過listview.getAdapter拿到我們自定義的adapter, 必須按照如下步驟:
復制代碼 代碼如下:PulmBaseAdapter adapter = (PulmBaseAdapter) ((HeaderViewListAdapter) getAdapter()).getWrappedAdapter();
參考
1.PagingListView
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
今天學習了一下 u-boot-2012.04.01 的第一階段,發現與 u-boot1.1.6 的差異還是很大的,尤其是在代碼重定位方面。在 u-boot1.1.6 中鏈
寫程序的過程中,想法總會不斷地變,有時候會很糾結,到底做哪種效果好,怎麼做好呢? 就比如這個音樂播放器,我原來的想法是把列表頁面跟歌詞頁面放在同一個Activity中的兩
這就是開源的好處,通過Github、各大論壇和技術博客,你會發現很多對你有用的資源。對於做技術的同學來說,深入研究一門技術很重要,但是適當的擴展自己的視野,了解他人的一些
之前寫了兩篇關於自定義view的文章,本篇講講自定義ViewGroup的實現。我們知道ViewGroup就是View的容器類,我們經常用的LinearLayout,Rel