UI之RecyclerView加載更多

效果圖

anglerNRD90Tzhuleiyue09212016183407.gif

RecyclerView實現(xiàn)加載更多可分為兩個步驟

  1. RecyclerView滑動到底部的監(jiān)聽
  2. 給RecyclerView添加footer鲸匿,展示加載狀態(tài)

一、給RecyclerView添加ScrollListener監(jiān)聽滑動到底部

1. 繼承RecyclerView添加滑動監(jiān)聽

public class LoadMoreRecyclerView extends RecyclerView {

    public LoadMoreRecyclerView(Context context) {
        this(context, null);
    }

    public LoadMoreRecyclerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoadMoreRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        addOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });
    }
}

2. 判斷滑動到底部

2.1 判斷滑動方向

給LoadMoreRecyclerView添加屬性

/**
 * 是否是向下滑動
 */
private boolean isScrollDown;

在OnScrollListener中的onScrolled方法中判斷RecyclerView的滑動方向

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
    super.onScrolled(recyclerView, dx, dy);
    isScrollDown = dy > 0;
}

RecyclerView在滑動完成的時候會調(diào)用onScrolled方法币他,其中dx和dy分別表示水平滑動和垂直滑動的距離
如果dy>0表示向下滑動

2.2 判斷滑動到底部

在OnScrollListener中的onScrollStateChanged方法中判斷RecyclerView是否滑動到底部

@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
    super.onScrollStateChanged(recyclerView, newState);
    if (newState == RecyclerView.SCROLL_STATE_IDLE) {// RecyclerView已經(jīng)停止滑動
        int lastVisibleItem;
        // 獲取RecyclerView的LayoutManager
        LayoutManager layoutManager = recyclerView.getLayoutManager();
        // 獲取到最后一個可見的item
        if (layoutManager instanceof LinearLayoutManager) {// 如果是LinearLayoutManager
            lastVisibleItem = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {// 如果是StaggeredGridLayoutManager
            int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
            ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
            lastVisibleItem = findMax(into);
        } else {// 否則拋出異常
            throw new RuntimeException("Unsupported LayoutManager used");
        }
        // 獲取item的總數(shù)
        int totalItemCount = layoutManager.getItemCount();
            /*
                并且最后一個可見的item為最后一個item
                并且是向下滑動
             */
        if (lastVisibleItem >= totalItemCount - 1 && isScrollDown) {
            // 此處調(diào)用加載更多回調(diào)接口的回調(diào)方法
        }
    }
}

/**
 * 獲取數(shù)組中的最大值
 *
 * @param lastPositions 需要找到最大值的數(shù)組
 * @return 數(shù)組中的最大值
 */
private int findMax(int[] lastPositions) {
    int max = lastPositions[0];
    for (int value : lastPositions) {
        if (value > max) {
            max = value;
        }
    }
    return max;
}

RecyclerView的滑動狀態(tài)改變時會調(diào)用onScrollStateChanged方法鳍置,其中newState表示RecyclerView的滑動狀態(tài)

  • SCROLL_STATE_IDLE 表示RecyclerView沒有在滑動
  • SCROLL_STATE_DRAGGING 表示RecyclerView正在被拖著滑動
  • SCROLL_STATE_SETTLING 表示RecyclerView正在滑動但是沒有外部控制

3. 添加加載更多的回調(diào)接口

3.1 創(chuàng)建加載更多回調(diào)接口
/**
 * 加載更多的回調(diào)接口
 */
public interface OnLoadMore {
    void onLoad();
}
3.2 給RecyclerView添加設置回調(diào)的方法
/**
 * 加載更多的回調(diào)接口
 */
private OnLoadMore mOnLoadMore;

/**
 * 是否加載更多
 */
private boolean mIsLoadMore;

/**
 * 設置加載更多的回調(diào)接口
 * @param onLoadMore 加載更多的回調(diào)接口
 */
public void setOnLoadMore(OnLoadMore onLoadMore) {
    // 是否加載更多置為true
    this.mIsLoadMore = true;
    this.mOnLoadMore = onLoadMore;
}

在判斷滑動到底部的地方調(diào)用回調(diào)接口的回調(diào)方法

二登疗、給RecyclerView添加footer展示加載狀態(tài)

1. 繼承Adapter添加footer

private static class LoadMoreAdapter extends Adapter {
    /**
     * 添加footer的類型
     */
    private static final int TYPE_FOOTER = -1;
    /**
     * footer的狀態(tài)
     */
    protected int mLoadMoreStatus = STATUS_PREPARE;
    /**
     * footer的點擊事件
     */
    protected View.OnClickListener mListener;
    /**
     * 正常item的adapter
     */
    private Adapter mAdapter;
    /**
     * 是否加載更多
     */
    private boolean mIsLoadMore;
    /**
     * GridLayoutManager
     */
    private GridLayoutManager mGridLayoutManager;

    public LoadMoreAdapter(Adapter adapter, boolean isLoadMore) {
        this.mAdapter = adapter;
        this.mIsLoadMore = isLoadMore;
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
            this.mGridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
        }
    }

    @Override
    public void onViewAttachedToWindow(ViewHolder holder) {
        super.onViewAttachedToWindow(holder);
        if (mIsLoadMore) {// 如果加載更多
            if (mGridLayoutManager != null) {
                mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                    @Override
                    public int getSpanSize(int position) {
                        // 當position為最后一項時返回spanCount
                        return position == getItemCount() - 1 ? mGridLayoutManager.getSpanCount() : 1;
                    }
                });
            }
            ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
            if (params instanceof StaggeredGridLayoutManager.LayoutParams) {
                if (holder.getLayoutPosition() == getItemCount() - 1) { // 當position為最后一項時這是FullSpan為true
                    ((StaggeredGridLayoutManager.LayoutParams) params).setFullSpan(true);
                }
            }
        }
    }

    /**
     * 如果是footer類型,創(chuàng)建FooterView
     * 否則創(chuàng)建正常的ItemView
     */
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (mIsLoadMore && viewType == TYPE_FOOTER) {
            return onCreateFooterViewHolder(parent);
        } else {
            return mAdapter.onCreateViewHolder(parent, viewType);
        }
    }

    /**
     * 如果加載更多且是footer類型,則展示footer
     * 否則展示正常的item
     */
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        if (mIsLoadMore && getItemViewType(position) == TYPE_FOOTER) {
            bindFooterItem(holder);
        } else {
            mAdapter.onBindViewHolder(holder, position);
        }
    }

    /**
     * 如果加載更多
     * 如果正常的item為0  則不顯示footer,返回0
     * 如果正常的item不為0  則返回mAdapter.getItemCount() + 1
     * 如果不加載更多
     * 返回mAdapter.getItemCount()
     */
    @Override
    public int getItemCount() {
        return mIsLoadMore ? mAdapter.getItemCount() == 0 ? 0 : mAdapter.getItemCount() + 1 : mAdapter.getItemCount();
    }

    /**
     * 如果加載更多且position為最有一個,則返回類型為footer
     * 否則返回mAdapter.getItemViewType(position)
     */
    @Override
    public int getItemViewType(int position) {
        if (mIsLoadMore && position == getItemCount() - 1) {
            return TYPE_FOOTER;
        } else {
            return mAdapter.getItemViewType(position);
        }
    }

    /**
     * 設置footer的狀態(tài),并通知更改
     */
    void setLoadMoreStatus(int status) {
        this.mLoadMoreStatus = status;
        notifyItemChanged(getItemCount() - 1);
    }

    /**
     * 設置footer的點擊重試事件
     * @param listener
     */
    public void setRetryListener(View.OnClickListener listener) {
        this.mListener = listener;
    }

    public int getLoadMoreStatus() {
        return this.mLoadMoreStatus;
    }

    /**
     * 創(chuàng)建FooterView
     */
    public ViewHolder onCreateFooterViewHolder(ViewGroup parent) {
        return new FooterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.footer_view_sample, parent, false));
    }

    /**
     * 設置是否加載更多
     */
    public void setIsLoadMore(boolean isLoadMore) {
        this.mIsLoadMore = isLoadMore;
    }

    /**
     * 展示FooterView
     * @param holder
     */
    protected void bindFooterItem(ViewHolder holder) {
        FooterViewHolder footerViewHolder = (FooterViewHolder) holder;
        switch (mLoadMoreStatus) {
            case STATUS_LOADING:
                holder.itemView.setVisibility(View.VISIBLE);
                footerViewHolder.pb.setVisibility(View.VISIBLE);
                footerViewHolder.tv.setText("正在加載更多...");
                break;
            case STATUS_EMPTY:
                holder.itemView.setVisibility(View.VISIBLE);
                footerViewHolder.pb.setVisibility(View.GONE);
                footerViewHolder.tv.setText("沒有更多了");
                holder.itemView.setOnClickListener(null);
                break;
            case STATUS_ERROR:
                holder.itemView.setVisibility(View.VISIBLE);
                footerViewHolder.pb.setVisibility(View.GONE);
                footerViewHolder.tv.setText("加載出錯,點擊重試");
                holder.itemView.setOnClickListener(mListener);
                break;
            case STATUS_PREPARE:
                holder.itemView.setVisibility(View.INVISIBLE);
                break;
            case STATUS_DISMISS:
                holder.itemView.setVisibility(GONE);
        }
    }
}

static class FooterViewHolder extends RecyclerView.ViewHolder {
    ProgressBar pb;
    TextView tv;

    public FooterViewHolder(View itemView) {
        super(itemView);
        pb = (ProgressBar) itemView.findViewById(R.id.pb_footer_view);
        tv = (TextView) itemView.findViewById(R.id.tv_footer_view);
    }
}

footer_view_sample.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal"
    android:orientation="horizontal"
    android:padding="16dp">

    <ProgressBar
        android:id="@+id/pb_footer_view"
        style="@android:style/Widget.ProgressBar.Small"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tv_footer_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:gravity="center"
        android:text="正在加載更多..." />

</LinearLayout>

2. 設置在GridLayoutManager和StaggeredGridLayoutManager下footer撐滿一行

2.1 GridLayoutManager

重寫Adapter的onAttachedToRecyclerView娱节,獲取GridLayoutManager

/**
 * GridLayoutManager
 */
private GridLayoutManager mGridLayoutManager;

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
        this.mGridLayoutManager = (GridLayoutManager) recyclerView.getLayoutManager();
    }
}

重寫onViewAttachedToWindow挠蛉,給GridLayoutManager設置SpanSizeLookup

@Override
public void onViewAttachedToWindow(ViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    if (mIsLoadMore) {// 如果加載更多
        if (mGridLayoutManager != null) {
            mGridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    // 當position為最后一項時返回spanCount
                    return position == getItemCount() - 1 ? mGridLayoutManager.getSpanCount() : 1;
                }
            });
        }
    }
}
2.2 StaggeredGridLayoutManager

重寫onViewAttachedToWindow

@Override
public void onViewAttachedToWindow(ViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    if (mIsLoadMore) {// 如果加載更多
        ViewGroup.LayoutParams params = holder.itemView.getLayoutParams();
        if (params instanceof StaggeredGridLayoutManager.LayoutParams) {
            if (holder.getLayoutPosition() == getItemCount() - 1) { // 當position為最后一項時這是FullSpan為true
                ((StaggeredGridLayoutManager.LayoutParams) params).setFullSpan(true);
            }
        }
    }
}

2. 在LoadMoreRecyclerView的滑動監(jiān)聽中添加判斷并設置footer狀態(tài)

private void init() {
    addOnScrollListener(new OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (mOnLoadMore != null) {// 如果加載更多的回調(diào)接口不為空
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {// RecyclerView已經(jīng)停止滑動
                    ...
                    /*
                        如果RecyclerView的footer的狀態(tài)為準備中
                        并且最后一個可見的item為最后一個item
                        并且是向下滑動
                     */
                    if (mLoadMoreAdapter.getLoadMoreStatus() == STATUS_PREPARE
                            && lastVisibleItem >= totalItemCount - 1 && isScrollDown) {
                        // 設置RecyclerView的footer的狀態(tài)為加載中
                        mLoadMoreAdapter.setLoadMoreStatus(STATUS_LOADING);
                        // 觸發(fā)加載更多的回調(diào)方法
                        mOnLoadMore.onLoad();
                    }
                }
            }
        }

        ...
    });
}

3. 在LoadMoreRecyclerView中重寫setAdapter方法,設置footer狀態(tài)和點擊事件

private LoadMoreAdapter mLoadMoreAdapter;

public void setAdapter(Adapter adapter) {
    this.mLoadMoreAdapter = new LoadMoreAdapter(adapter, mIsLoadMore);
    this.mLoadMoreAdapter.setRetryListener(retryListener);
    super.setAdapter(mLoadMoreAdapter);
}

/**
 * 設置footer的狀態(tài)
 */
public void setLoadMoreStatus(int status) {
    if (mLoadMoreAdapter != null) {
        mLoadMoreAdapter.setLoadMoreStatus(status);
    }
}

/**
 * footer的重試點擊事件
 */
View.OnClickListener retryListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mLoadMoreAdapter.setLoadMoreStatus(STATUS_LOADING);
        mOnLoadMore.onLoad();
    }
};

** ps: 關(guān)于onViewAttachedToWindow和onViewAttachedToWindow的說明 **

  • onViewAttachedToWindow方法在RecyclerView調(diào)用setAdapter方法是被調(diào)用

  • onViewAttachedToWindow方法在RecyclerView展示在界面上是被調(diào)用

    ** 為了保證LoadMoreRecyclerView中setOnLoadMore和setAdapter調(diào)用的無序性肄满,不能在onViewAttachedToWindow方法中設置GridLayoutManager的SpanSizeLookup **

三谴古、使用注意

** 因為重寫了RecyclerView的setAdapter方法质涛,把傳如的adapter包裝之后重新設置,所以在調(diào)用notifyDataSetChanged()等方法時掰担,不能直接用自己創(chuàng)建adapter調(diào)用汇陆,而要使用RecyclerView.getAdapter調(diào)用。**

demo地址:https://github.com/zly394/LoadMoreRecyclerView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末带饱,一起剝皮案震驚了整個濱河市毡代,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌勺疼,老刑警劉巖教寂,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異执庐,居然都是意外死亡酪耕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門耕肩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來因妇,“玉大人问潭,你說我怎么就攤上這事猿诸。” “怎么了狡忙?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵梳虽,是天一觀的道長。 經(jīng)常有香客問我灾茁,道長窜觉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任北专,我火速辦了婚禮禀挫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘拓颓。我一直安慰自己语婴,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布驶睦。 她就那樣靜靜地躺著砰左,像睡著了一般。 火紅的嫁衣襯著肌膚如雪场航。 梳的紋絲不亂的頭發(fā)上缠导,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音溉痢,去河邊找鬼僻造。 笑死憋他,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的髓削。 我是一名探鬼主播举瑰,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蔬螟!你這毒婦竟也來了此迅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤旧巾,失蹤者是張志新(化名)和其女友劉穎耸序,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鲁猩,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡坎怪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了廓握。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搅窿。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖隙券,靈堂內(nèi)的尸體忽然破棺而出男应,到底是詐尸還是另有隱情,我是刑警寧澤娱仔,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布沐飘,位于F島的核電站,受9級特大地震影響牲迫,放射性物質(zhì)發(fā)生泄漏耐朴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一盹憎、第九天 我趴在偏房一處隱蔽的房頂上張望筛峭。 院中可真熱鬧,春花似錦陪每、人聲如沸影晓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俯艰。三九已至,卻和暖如春锌订,著一層夾襖步出監(jiān)牢的瞬間竹握,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工辆飘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留啦辐,地道東北人谓传。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像芹关,于是被迫代替她去往敵國和親续挟。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,262評論 25 707
  • 簡介: 提供一個讓有限的窗口變成一個大數(shù)據(jù)集的靈活視圖侥衬。 術(shù)語表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,174評論 0 16
  • 很多時候诗祸,項目中都會有列表加載更多的場景,這次我們讓RecyclerView輕松擁有加載更多的功能轴总。雖然已有許多類...
    SheHuan閱讀 28,760評論 45 184
  • 這個世界沒有任何個體不具有存在的意義直颅。如果當真是這樣,那么那個問題(究竟是我們改變了世界還是世界改變了我們)就有了...
    大婷和小貝閱讀 667評論 0 1
  • 看了伯樂在線里的一篇文章,自己面試的時候也被問到了.挑了重點來記錄記錄.點擊查看 為什么HTML5里面我們不需要D...
    神奇的少年閱讀 473評論 0 0