實(shí)現(xiàn)下拉刷新和加載更多的ListView就這么簡單

概述

ListView絕對(duì)是我們開發(fā)中的高頻的控件。目前很多人建議使用ReclyeView替換ListViewRecycleView更加靈活,支持豐富的布局(橫向列表,豎線列表,瀑布流,網(wǎng)格等),RecycleView.AdaperBaseAdapter做了更好的封裝,但是有不少基礎(chǔ)功能需要自己實(shí)現(xiàn),如分割線,點(diǎn)擊等等。故在實(shí)現(xiàn)簡單列表頁時(shí)我們還是優(yōu)先選擇ListView。通常需要支持下拉刷新,重新加載第一頁數(shù)據(jù);滑動(dòng)到底部或點(diǎn)擊加載更多時(shí),加載下一頁的數(shù)據(jù);在沒有更多數(shù)據(jù)時(shí),還可提醒用戶。github應(yīng)該有很多酷炫項(xiàng)目支持上述功能,但是跟自己預(yù)期的或多或少有些出入。

  1. 只支持下拉刷新
  2. 封裝太復(fù)雜,支持太多,很多或許用不到

分析

下拉刷新加載更多 是兩個(gè)功能,官方v4庫的SwipeRefreshLayout已經(jīng)支持下拉刷新功能,只需在ListView外嵌SwipeRefreshLayout即可。那剩下的問題就是如何實(shí)現(xiàn)加載更多功能。我們先思考加載更多觸發(fā)的時(shí)機(jī)是什么?

  1. 滾動(dòng)到底部,自動(dòng)觸發(fā)或者點(diǎn)擊加載更多或者上拉刷新
  2. 若加載顯示的數(shù)據(jù)未鋪滿一屏幕,未出現(xiàn)滾動(dòng)時(shí),第一種情況就觸發(fā)不了。咋辦?這時(shí)列表底部應(yīng)該有一個(gè)點(diǎn)擊加載跟多控件。

上拉刷新實(shí)現(xiàn)比較麻煩,且交互體驗(yàn)不佳。我更加推崇滾動(dòng)到底部自動(dòng)加載,但是某些場景(如加載耗很多流量)可能不是很適合。可以優(yōu)化為前N頁自動(dòng)加載,之后只能點(diǎn)擊加載更多控件來加載下一頁數(shù)據(jù)。數(shù)據(jù)集通常是有限的,加載到最后一頁時(shí)應(yīng)該提示用戶,并且不再可觸發(fā)加載下一頁。

實(shí)現(xiàn)

直接上代碼:

public class SimpleListView extends SwipeRefreshLayout {
    private ListView mListView;
    private LoadMoreStatus mLoadMoreStatus = LoadMoreStatus.CLICK_TO_LOAD;
    private OnLoadListener mOnLoadListener;
    private TextView mLoadMoreView;
    private AbsListView.OnScrollListener mOnScrollListener;
    private View mEmptyView;
    private ListAdapter mAdapter;

    /**
     * 加載更多狀態(tài)
     */
    public static enum LoadMoreStatus {
        /**
         * 點(diǎn)擊加載更多
         */
        CLICK_TO_LOAD,
        /**
         * 正在加載
         */
        LOADING,
        /**
         * 沒有更多內(nèi)容了
         */
        LOADED_ALL
    }

    /**
     * 加載監(jiān)聽器
     */
    public static interface OnLoadListener {
        /**
         * 下來刷新或者加載更多時(shí)觸發(fā)該回調(diào)
         *
         * @param isRefresh true為下拉刷新 false為加載更多
         */
        public void onLoad(boolean isRefresh);
    }

    public SimpleListView(Context context) {
        super(context);
        init(context, null);
    }

    public SimpleListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mListView = new ListView(context, attrs);
        addView(mListView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        mListView.setOnScrollListener(new AbsListView.OnScrollListener() {
            private boolean mIsEnd = false;

            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                if (mOnScrollListener != null) {
                    mOnScrollListener.onScrollStateChanged(view, scrollState);
                }
                if (scrollState == SCROLL_STATE_IDLE) {
                    //1:到達(dá)底部 2:底部當(dāng)前可以加載更多 3:頂部不在刷新中狀態(tài)
                    if (mIsEnd && mLoadMoreStatus == LoadMoreStatus.CLICK_TO_LOAD && !isRefreshing()) {
                        setLoadMoreStatus(LoadMoreStatus.LOADING);
                        if (mLoadMoreStatus != null) {
                            mOnLoadListener.onLoad(false);
                        }
                    }
                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                if (mOnScrollListener != null) {
                    mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
                }
                if (firstVisibleItem + visibleItemCount >= totalItemCount - 1) {
                    mIsEnd = true;
                } else {
                    mIsEnd = false;
                }
            }
        });

        super.setOnRefreshListener(new OnRefreshListener() {
            @Override
            public void onRefresh() {
                if (mLoadMoreStatus != LoadMoreStatus.LOADING) {
                    if (mOnLoadListener != null) {
                        mOnLoadListener.onLoad(true);
                    }
                } else {
                    SimpleListView.super.setRefreshing(false);
                }
            }
        });

    }

    public void addHeaderView(View view) {
        mListView.addHeaderView(view);
    }

    public void addHeaderView(View v, Object data, boolean isSelectable) {
        mListView.addHeaderView(v, data, isSelectable);
    }

    public void addFooterView(View view) {
        mListView.addFooterView(view);
    }

    public void addFooterView(View v, Object data, boolean isSelectable) {
        mListView.addFooterView(v, data, isSelectable);
    }

    public void setOnScrollListener(AbsListView.OnScrollListener listener) {
        mOnScrollListener = listener;
    }

    public void setOnItemClickListener(AdapterView.OnItemClickListener listener){
        mListView.setOnItemClickListener(listener);
    }

    public void setEmptyView(View emptyView) {
        if (emptyView != null) {
            mEmptyView = emptyView;
            if (mAdapter != null && mAdapter.getCount() > 0) {
                mEmptyView.setVisibility(View.GONE);
            } else {
                mEmptyView.setVisibility(View.VISIBLE);
            }
//            mListView.setEmptyView(emptyView);
        }
    }

    @Override
    @Deprecated
    public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
    }

    @Override
    @Deprecated
    public void setRefreshing(boolean refreshing) {
    }

    public void setAdapter(final ListAdapter adapter) {
        if (adapter == null) {
            return;
        }
        mAdapter = adapter;
        if (mLoadMoreView == null) {
            mLoadMoreView = new TextView(getContext());
            mLoadMoreView.setTextColor(0xff333333);
            mLoadMoreView.setTextSize(14);
            mLoadMoreView.setGravity(Gravity.CENTER);
            int count = adapter.getCount();
            mLoadMoreView.setVisibility(count == 0 ? View.GONE : View.VISIBLE);
            if (mEmptyView != null) {
                mEmptyView.setVisibility(count == 0 ? View.VISIBLE : View.GONE);
            }
            mLoadMoreView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (mLoadMoreStatus == LoadMoreStatus.CLICK_TO_LOAD && !isRefreshing()) {
                        setLoadMoreStatus(LoadMoreStatus.LOADING);
                        if (mLoadMoreStatus != null) {
                            mOnLoadListener.onLoad(false);
                        }
                    }
                }
            });
            mLoadMoreView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT, getResources().getDimensionPixelOffset(R.dimen.dp10) * 4));
            mListView.addFooterView(mLoadMoreView);
        }
        mListView.setAdapter(adapter);
        adapter.registerDataSetObserver(new DataSetObserver() {
            @Override
            public void onChanged() {
                int count = adapter.getCount();
                mLoadMoreView.setVisibility(count == 0 ? View.GONE : View.VISIBLE);
                if (mEmptyView != null) {
                    mEmptyView.setVisibility(count == 0 ? View.VISIBLE : View.GONE);
                }
            }
        });
    }

    private void setLoadMoreStatus(LoadMoreStatus status) {
        mLoadMoreStatus = status;
        if (mLoadMoreView != null) {
            if (mLoadMoreStatus == LoadMoreStatus.LOADED_ALL) {
                mLoadMoreView.setText("沒有更多內(nèi)容了");
            } else if (mLoadMoreStatus == LoadMoreStatus.LOADING) {
                mLoadMoreView.setText("正在加載...");
            } else {
                mLoadMoreView.setText("點(diǎn)擊加載更多");
            }
        }
    }

    public void setOnLoadListener(OnLoadListener listener) {
        mOnLoadListener = listener;
    }

    public void finishLoad(boolean loadAll) {
        super.setRefreshing(false);
        setLoadMoreStatus(loadAll ? LoadMoreStatus.LOADED_ALL : LoadMoreStatus.CLICK_TO_LOAD);
    }

}

基本思路如下:

  1. 繼承SwipeRefreshLayout,內(nèi)嵌一個(gè)ListView
  2. ListView添加一個(gè)footerView,做為加載更多控件mLoadMoreView
  3. mLoadMoreView有三種狀態(tài)點(diǎn)擊加載更多正在加載沒有更多內(nèi)容
  4. finishLoad(boolean loadAll) 完成數(shù)據(jù)加載,關(guān)閉加載狀態(tài)時(shí)調(diào)用,其中 loadAll表示是否加載完所有數(shù)據(jù)。
  5. OnLoadListener接口,下來刷新或者加載更多時(shí)觸發(fā)該回調(diào),其中的方法是

isRefresh true為下拉刷新 false為加載更多
public void onLoad(boolean isRefresh);

還有一些實(shí)現(xiàn)細(xì)節(jié)不再明說,有興趣的同學(xué)可以運(yùn)行一下demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容