Android上拉載入更多ListView——PulmListView

來源:互聯網
上載者:User

標籤:

思路

今天帶大家實現一個上拉載入更多的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"        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(關聯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

Android上拉載入更多ListView——PulmListView

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.