如何實現 Android ListView『上拉加載更多』?

ListView上拉加載更多的UI需求

(1)向上滑動 ListView,當最後一個條目滾入屏幕時開始加載更多條目,在列表底部增長一個 footerView:一個 infinite progressBar,一個 textView 顯示 「Loading...」;
(2)根據數據加載的結果更新 view:
(2.1)若是已經沒有更多條目,則更新 footerView:僅包含一個 textView 顯示「No More」;
(2.2)若是成功獲取更多條目,則更新 ListView,同時移除(隱藏) footerView;
(2.3)若是加載失敗(網絡異常等緣由),移除(隱藏) footerView。html

綜上述,須要有一個 footerView,它包含兩種狀態:java

-------------------------------
|        @  Loading...        | (借用 @ 當作infinite progressBar)
-------------------------------

-------------------------------
|        No More              |
-------------------------------

這是一個挺簡單 UI 需求,比常見的實現方式少了一種狀態:android

-------------------------------
|        查看更多             |
-------------------------------

在這種狀態下,點擊 footerView 也能夠和『上拉』同樣加載更多條目。我對比了手Q和微信,手Q就多了這個『查看更多』的狀態(固然,必須在上拉時剛好讓它停在最後一個條目,否則上拉過頭後,就馬上變成『Loading...』)。
本需求並不須要這個狀態,因此下面的實現分析不會考慮它,因此總體實現相對簡單。git

爲了實現上述需求,須要考慮三個問題:github

  • 如何定義 footerView?微信

  • 什麼時候加載更多?網絡

  • 數據加載完畢後,如何更新視圖?ide

如何定義 footerView?

如上述,footerView 包含兩種狀態:加載中、沒有加載this

『加載中』包含兩個控件,infinite progressBar 和 textView,放進一個 LinearLayout;『沒有加載』只包含一個控件,textView,也把它放進一個 LinearLayout;而後把這兩個 LinearLayout 放到一個 FrameLayout 內。根據狀態決定顯示哪一個 LinearLayout。所以只須要一個 public method:spa

public class PullUpLoadListViewFooter extends LinearLayout {
    public enum State {
        LOADING,
        NOT_LOADING,
    }

    public void updateView(State state, String content) {}
}

什麼時候加載更多?

向上滑動 ListView,當最後一個條目滾入屏幕時開始加載更多條目。ListView 能夠監聽滾動事件,所以知道什麼時候加載更多。但數據加載的工做顯然應該交給控制器,也就是 ListView 的託管者好比 Activity 來完成。
因此,在 ListView 中定義一個接口,並在 ListView 滾動事件中回調這個接口方法:

public class PullUpLoadListView extends ListView {
    public interface OnPullUpLoadListener {
        void onPullUpLoading();
    }

    public void setOnPullUpLoadListener(OnPullUpLoadListener listener) {
        mOnPullUpLoadListener = listener;
    }

    private OnPullUpLoadListener mOnPullUpLoadListener;

    private OnScrollListener mOnScrollListener = new OnScrollListener() {
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            // Start a new loading when the last item scrolls into screen, instead of overriding method onTouchEvent.
            // 檢查是否到listView底部,檢查callbacks是否註冊:
            if (needLoad(...)) {
                startPullUpLoad();
            }
        }
    };
}

startPullUpLoad() 是listView 處理上拉加載的核心代碼:秀出顯示『Loading...』的footerView;設置標誌位表示『已經處於上拉加載狀態中』防止重複加載;回調。

private void startPullUpLoad() {
    if (mOnPullUpLoadListener != null) {
        // Show the foot view and update its state to LOADING.
        showFooterView();
        // Set flag
        mIsPullUpLoading = true;
        // Call the callback to notify the listView's hosted controller to load data.
        mOnPullUpLoadListener.onPullUpLoading();
    }
}

再由 Activity 實現該接口完成加載工做:

listView.setOnPullUpLoadListener(new PullUpLoadListView.OnPullUpLoadListener() {
    @Override
    public void onPullUpLoading() {
        if (shouldLoadMore) {
            // Loading more data
            new LoadDataAsyncTask().execute();
        } else {
            // Already has no more data
            // 下面會講到這個方法
            listView.onPullUpLoadFinished(true);
        }
    }
});

數據加載完畢後,如何更新視圖?

ListView 提供一個public方法,根據數據加載的結果更新視圖:

public class PullUpLoadListView extends ListView {
    // When loading finished, the controller should call this public method to update footer view.
    public void onPullUpLoadFinished(boolean hasNoMoreItems) {
        // Clear flag
        mIsPullUpLoading = false;

        if (hasNoMoreItems) {
            // when have no more items, update footer view to: NO MORE
            mFooterView.updateView(PullUpLoadListViewFooter.State.NOT_LOADING, FOOTER_VIEW_CONTENT_NO_MORE);
        } else {
            // The other cases: (1)Loading succeed and still has more items, (2)Loading failed,
            // should hide footer view.
            hideFooterView();
        }
    }
}

Activity 完成加載數據後,調用 ListView 提供的方法,並更新 adapter 數據集:

listView.onPullUpLoadFinished(false);
// Add more data to adapter and notify data set changed to update listView.
adapter.addMoreItems(newItems);

源碼實現總結

上述實現基於 nicolasjafelle/PagingListView,對 PagingListView.java 作了兩處較大改動:

(1)數據加載完成後,由PagingListView負責更新adapter,考慮到ListView可能並不清楚adapter的接口,因此仍是交給activity比較好

// PagingListView的實現
public void onFinishLoading(boolean hasMoreItems, List<? extends Object> {
    ...
    ((PagingBaseAdapter) adapter).addMoreItems(newItems);
}

(2)PagingListView維護了一個私有成員boolean hasMoreItems,而後在滾動事件回調onScroll(...)中,若是該值爲false,就不會加載更多數據。

我以爲不該該由ListView來維護『是否具備更多的item』,這樣會帶來一些困惑和額外的工做。好比該值爲false的狀況下,當外部清空list item後,必須重置 hasMoreItems,不然沒法繼續加載。

這樣邏輯顯得比較亂,而『是否能夠加載更多』,應該分紅兩部分:
由 ListView 判斷『沒有處於加載狀態』而且『已經滾到了最後一個條目』則容許加載;
由 Activity 判斷『還有更多的數據』則容許加載。

這樣就顯得清晰不少了。

你能夠從這裏獲取源碼:Learning_Android_Open_Source/Pull_Up_Load_ListView_Sample

以下的GIF演示了上拉加載的過程:
clipboard.png


版權聲明:《如何實現 Android ListView『上拉加載更多』?》由 WeiYi.Li 在 2016年01月12日寫做。著做權歸做者全部。商業轉載請聯繫做者得到受權,非商業轉載請註明出處。
文章連接:http://li2.me/2016/01/android-pull-up-lo...

相關文章
相關標籤/搜索