Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android上拉加載更多ListView——PulmListView

Android上拉加載更多ListView——PulmListView

編輯:關於Android編程

思路

今天帶大家實現一個上拉加載更多的ListView.GitHub傳送門:PulmListView, 歡迎大家fork&&star.

先帶大家理一下思路, 如果我們要實現一個上拉加載更多的ListView, 我們需要實現的功能包括:

一個自定義的ListView, 並且該ListView能夠判斷當前是否已經處於最底部. 一個自定義的FooterView, 用於在ListView加載更多的過程中進行UI展示. 關聯FooterView和ListView, 包括加載時機判斷、FooterView的顯示和隱藏. 提供一個加載更多的接口, 便於回調用戶真正加載更多的功能實現. 提供一個加載更多結束的回調方法, 用於添加用戶的最新數據並更新相關狀態標記和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);
    }
}

布局文件:




    

    

功能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 extends BaseAdapter {
    protected List items;

    public PulmBaseAdapter() {
        this.items = new ArrayList<>();
    }

    public PulmBaseAdapter(List items) {
        this.items = items;
    }

    public void addMoreItems(List 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的數據集合就必須分為兩步:

removeAllItems並進行notifyDataSetChanged. 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();

參考

PagingListView
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved