Android RecyclerView下拉刷新 & 上拉加載更多

封面

GitHub傳送門

1.寫在前面

本文主要實現(xiàn)的是上拉加載更多功能陶耍,下拉刷新使用的是Google官方的SwipeRefreshLayout控件贾费,因為在實現(xiàn)這個功能的時候走了不少彎路,所以在此記錄下來分享給大家质蕉,先看下效果圖:

上拉加載更多

2.實現(xiàn)

上拉加載更多功能實際上就是給RecyclerView增加一個FooterView餐禁,然后通過判斷是否滑動到了最后一條Item镀虐,來控制FooterView的顯示和隱藏,接下來我們來看下如何實現(xiàn):

Adapter添加FooterView

小二默穴,上代碼:

public class LoadMoreAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<String> dataList;

    // 普通布局
    private final int TYPE_ITEM = 1;
    // 腳布局
    private final int TYPE_FOOTER = 2;
    // 當前加載狀態(tài)怔檩,默認為加載完成
    private int loadState = 2;
    // 正在加載
    public final int LOADING = 1;
    // 加載完成
    public final int LOADING_COMPLETE = 2;
    // 加載到底
    public final int LOADING_END = 3;

    public LoadMoreAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    @Override
    public int getItemViewType(int position) {
        // 最后一個item設置為FooterView
        if (position + 1 == getItemCount()) {
            return TYPE_FOOTER;
        } else {
            return TYPE_ITEM;
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //進行判斷顯示類型,來創(chuàng)建返回不同的View
        if (viewType == TYPE_ITEM) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.adapter_recyclerview, parent, false);
            return new RecyclerViewHolder(view);

        } else if (viewType == TYPE_FOOTER) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.layout_refresh_footer, parent, false);
            return new FootViewHolder(view);
        }
        return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof RecyclerViewHolder) {
            RecyclerViewHolder recyclerViewHolder = (RecyclerViewHolder) holder;
            recyclerViewHolder.tvItem.setText(dataList.get(position));

        } else if (holder instanceof FootViewHolder) {
            FootViewHolder footViewHolder = (FootViewHolder) holder;
            switch (loadState) {
                case LOADING: // 正在加載
                    footViewHolder.pbLoading.setVisibility(View.VISIBLE);
                    footViewHolder.tvLoading.setVisibility(View.VISIBLE);
                    footViewHolder.llEnd.setVisibility(View.GONE);
                    break;

                case LOADING_COMPLETE: // 加載完成
                    footViewHolder.pbLoading.setVisibility(View.INVISIBLE);
                    footViewHolder.tvLoading.setVisibility(View.INVISIBLE);
                    footViewHolder.llEnd.setVisibility(View.GONE);
                    break;

                case LOADING_END: // 加載到底
                    footViewHolder.pbLoading.setVisibility(View.GONE);
                    footViewHolder.tvLoading.setVisibility(View.GONE);
                    footViewHolder.llEnd.setVisibility(View.VISIBLE);
                    break;

                default:
                    break;
            }
        }
    }

    @Override
    public int getItemCount() {
        return dataList.size() + 1;
    }

    private class RecyclerViewHolder extends RecyclerView.ViewHolder {

        TextView tvItem;

        RecyclerViewHolder(View itemView) {
            super(itemView);
            tvItem = (TextView) itemView.findViewById(R.id.tv_item);
        }
    }

    private class FootViewHolder extends RecyclerView.ViewHolder {

        ProgressBar pbLoading;
        TextView tvLoading;
        LinearLayout llEnd;

        FootViewHolder(View itemView) {
            super(itemView);
            pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_loading);
            tvLoading = (TextView) itemView.findViewById(R.id.tv_loading);
            llEnd = (LinearLayout) itemView.findViewById(R.id.ll_end);
        }
    }

    /**
     * 設置上拉加載狀態(tài)
     *
     * @param loadState 0.正在加載 1.加載完成 2.加載到底
     */
    public void setLoadState(int loadState) {
        this.loadState = loadState;
        notifyDataSetChanged();
    }
}

首先定義了布局和數(shù)據(jù)加載狀態(tài)的一些標志蓄诽,然后在getItemViewType方法中設置最后一個Item為FooterView薛训,在onCreateViewHolder方法中根據(jù)viewType來加載不同的布局,最后在onBindViewHolder方法中設置一下加載的狀態(tài)顯示就OK了仑氛,對了乙埃,由于多了一個FooterView,所以要記得在getItemCount方法的返回值中加上1锯岖。

到這里一個線性布局列表的Adapter就完成了介袜,注意,是線性布局列表(只有一列的那種)嚎莉,那網(wǎng)格布局怎么辦米酬,先看下這個Adapter在網(wǎng)格布局中使用會發(fā)生什么:

網(wǎng)格布局

可以看到加載更多的進度條顯示在了一個Item上,如果想要正常顯示的話趋箩,進度條需要橫跨兩個Item赃额,這該怎么辦呢,別擔心叫确,繼續(xù)往下看:

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
    if (manager instanceof GridLayoutManager) {
        final GridLayoutManager gridManager = ((GridLayoutManager) manager);
        gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                // 如果當前是footer的位置跳芳,那么該item占據(jù)2個單元格,正常情況下占據(jù)1個單元格
                return getItemViewType(position) == TYPE_FOOTER ? gridManager.getSpanCount() : 1;
            }
        });
    }
}

在Adapter中重寫onAttachedToRecyclerView方法竹勉,首先判斷當前是否為網(wǎng)格布局飞盆,然后給GridLayoutManager設置一個SpanSizeLookup,這是一個抽象類,里面有一個抽象方法getSpanSize吓歇,這個方法的返回值決定了每個Item占據(jù)的單元格數(shù)孽水。

以上文為例,是一個兩列的網(wǎng)格布局城看,如果當前Item是FooterView的話需要占據(jù)兩個單元格才能橫向充滿屏幕女气,所以需要返回2(GridLayoutManager的getSpanCount方法獲取到的是當前一行中單元格的數(shù)量),正常情況下每個Item占據(jù)一個單元格测柠。

RecyclerView設置滑動監(jiān)聽

設置好FooterView之后炼鞠,我們還需要判斷一下什么時候顯示出來,這就需要對RecyclerView設置一下滑動監(jiān)聽轰胁,當滑動到最后一個Item的時候谒主,顯示加載更多UI并且開始請求下一頁列表的數(shù)據(jù),看下代碼:

public abstract class EndlessRecyclerOnScrollListener extends RecyclerView.OnScrollListener {

    //用來標記是否正在向上滑動
    private boolean isSlidingUpward = false;

    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
        // 當不滑動時
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            //獲取最后一個完全顯示的itemPosition
            int lastItemPosition = manager.findLastCompletelyVisibleItemPosition();
            int itemCount = manager.getItemCount();

            // 判斷是否滑動到了最后一個item赃阀,并且是向上滑動
            if (lastItemPosition == (itemCount - 1) && isSlidingUpward) {
                //加載更多
                onLoadMore();
            }
        }
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        // 大于0表示正在向上滑動霎肯,小于等于0表示停止或向下滑動
        isSlidingUpward = dy > 0;
    }

    /**
     * 加載更多回調(diào)
     */
    public abstract void onLoadMore();
}

代碼中已經(jīng)寫了很全的注釋,重點看下onScrolled這個回調(diào)方法凹耙,里面有dx姿现、dy這兩個參數(shù),當向上滑動的時候dy是大于0的肖抱,向左滑動的時候dx是大于0的备典,反方向滑動則小于0,所以這段代碼稍稍修改一下就可以適用于橫向滑動列表的監(jiān)聽意述。

在Activity中使用

準備工作已經(jīng)完成了提佣,接下來看看如何使用吧:

public class LoadMoreActivity extends AppCompatActivity {

    private RecyclerView recyclerView;
    private LoadMoreAdapter loadMoreAdapter;
    private List<String> dataList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recyclerview);

        init();
    }

    private void init() {
        recyclerView = (RecyclerView) findViewById(R.id.recycler_view);

        // 模擬獲取數(shù)據(jù)
        getData();
        loadMoreAdapter = new LoadMoreAdapter(dataList);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(loadMoreAdapter);

        // 設置加載更多監(jiān)聽
        recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener() {
            @Override
            public void onLoadMore() {
                loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING);

                if (dataList.size() < 52) {
                    // 模擬獲取網(wǎng)絡數(shù)據(jù),延時1s
                    new Timer().schedule(new TimerTask() {
                        @Override
                        public void run() {
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    getData();
                                    loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING_COMPLETE);
                                }
                            });
                        }
                    }, 1000);
                } else {
                    // 顯示加載到底的提示
                    loadMoreAdapter.setLoadState(loadMoreAdapter.LOADING_END);
                }
            }
        });
    }

    private void getData() {
        char letter = 'A';
        for (int i = 0; i < 26; i++) {
            dataList.add(String.valueOf(letter));
            letter++;
        }
    }
}

調(diào)用RecyclerView的addOnScrollListener方法設置一下加載更多監(jiān)聽荤崇,在onLoadMore回調(diào)方法中拌屏,首先顯示正在加載進度UI,然后模擬獲取網(wǎng)絡數(shù)據(jù)术荤,完成之后隱藏加載進度UI倚喂,加載完兩頁數(shù)據(jù)之后顯示到底了的提示。

3.封裝

到這里瓣戚,我們已經(jīng)完成了RecyclerView的上拉加載更多功能端圈,但是大部分的邏輯都寫在了Adapter中,這樣每寫一個Adapter都要寫一遍加載邏輯子库,這是很不優(yōu)雅的舱权,接下來我們對加載更多功能做一個封裝,使其和Adapter完全解構(gòu)仑嗅,看下代碼:

public class LoadMoreWrapper extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private RecyclerView.Adapter adapter;

    // 普通布局
    private final int TYPE_ITEM = 1;
    // 腳布局
    private final int TYPE_FOOTER = 2;
    // 當前加載狀態(tài)宴倍,默認為加載完成
    private int loadState = 2;
    // 正在加載
    public final int LOADING = 1;
    // 加載完成
    public final int LOADING_COMPLETE = 2;
    // 加載到底
    public final int LOADING_END = 3;

    public LoadMoreWrapper(RecyclerView.Adapter adapter) {
        this.adapter = adapter;
    }

    @Override
    public int getItemViewType(int position) {
        // 最后一個item設置為FooterView
        if (position + 1 == getItemCount()) {
            return TYPE_FOOTER;
        } else {
            return TYPE_ITEM;
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //進行判斷顯示類型张症,來創(chuàng)建返回不同的View
        if (viewType == TYPE_FOOTER) {
            View view = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.layout_refresh_footer, parent, false);
            return new FootViewHolder(view);
        } else {
            return adapter.onCreateViewHolder(parent, viewType);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof FootViewHolder) {
            FootViewHolder footViewHolder = (FootViewHolder) holder;
            switch (loadState) {
                case LOADING: // 正在加載
                    footViewHolder.pbLoading.setVisibility(View.VISIBLE);
                    footViewHolder.tvLoading.setVisibility(View.VISIBLE);
                    footViewHolder.llEnd.setVisibility(View.GONE);
                    break;

                case LOADING_COMPLETE: // 加載完成
                    footViewHolder.pbLoading.setVisibility(View.INVISIBLE);
                    footViewHolder.tvLoading.setVisibility(View.INVISIBLE);
                    footViewHolder.llEnd.setVisibility(View.GONE);
                    break;

                case LOADING_END: // 加載到底
                    footViewHolder.pbLoading.setVisibility(View.GONE);
                    footViewHolder.tvLoading.setVisibility(View.GONE);
                    footViewHolder.llEnd.setVisibility(View.VISIBLE);
                    break;

                default:
                    break;
            }
        } else {
            adapter.onBindViewHolder(holder, position);
        }
    }

    @Override
    public int getItemCount() {
        return adapter.getItemCount() + 1;
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        super.onAttachedToRecyclerView(recyclerView);
        RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
        if (manager instanceof GridLayoutManager) {
            final GridLayoutManager gridManager = ((GridLayoutManager) manager);
            gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    // 如果當前是footer的位置,那么該item占據(jù)2個單元格鸵贬,正常情況下占據(jù)1個單元格
                    return getItemViewType(position) == TYPE_FOOTER ? gridManager.getSpanCount() : 1;
                }
            });
        }
    }

    private class FootViewHolder extends RecyclerView.ViewHolder {

        ProgressBar pbLoading;
        TextView tvLoading;
        LinearLayout llEnd;

        FootViewHolder(View itemView) {
            super(itemView);
            pbLoading = (ProgressBar) itemView.findViewById(R.id.pb_loading);
            tvLoading = (TextView) itemView.findViewById(R.id.tv_loading);
            llEnd = (LinearLayout) itemView.findViewById(R.id.ll_end);
        }
    }

    /**
     * 設置上拉加載狀態(tài)
     *
     * @param loadState 0.正在加載 1.加載完成 2.加載到底
     */
    public void setLoadState(int loadState) {
        this.loadState = loadState;
        notifyDataSetChanged();
    }
}

乍一看好像和上文中的LoadMoreAdapter沒什么區(qū)別俗他,都是繼承了RecyclerView.Adapter并實現(xiàn)了其中的一些方法,但是仔細看會發(fā)現(xiàn)阔逼,構(gòu)造方法中的參數(shù)變成了RecyclerView.Adapter拯辙,在LoadMoreWrapper中我們只處理加載更多功能相關的邏輯,其他邏輯交由Adapter本身處理颜价,相當于擴展了Adapter的一些功能,嗯诉濒,這種方式還有一個學名周伦,叫【裝飾者模式】。

封裝之后未荒,Adapter中的代碼變成了這樣:

public class LoadMoreWrapperAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<String> dataList;

    public LoadMoreWrapperAdapter(List<String> dataList) {
        this.dataList = dataList;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.adapter_recyclerview, parent, false);
        return new RecyclerViewHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        RecyclerViewHolder recyclerViewHolder = (RecyclerViewHolder) holder;
        recyclerViewHolder.tvItem.setText(dataList.get(position));
    }

    @Override
    public int getItemCount() {
        return dataList.size();
    }

    private class RecyclerViewHolder extends RecyclerView.ViewHolder {

        TextView tvItem;

        RecyclerViewHolder(View itemView) {
            super(itemView);
            tvItem = (TextView) itemView.findViewById(R.id.tv_item);
        }
    }
}

瞬間減少了一大半专挪,使用起來也很簡單,在原有Adapter的基礎上包上一層就可以了:

LoadMoreWrapperAdapter loadMoreWrapperAdapter = new LoadMoreWrapperAdapter(dataList);
LoadMoreWrapper loadMoreWrapper = new LoadMoreWrapper(loadMoreWrapperAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(loadMoreWrapper);

4.寫在最后

源碼已經(jīng)上傳到GitHub上了片排,歡迎Fork寨腔,覺得還不錯就Start一下吧!

GitHub傳送門

點我下載本文Demo的Apk

功能已集成至:
《Android開源項目 RecyclerViewHelper 上拉加載更多/頭尾布局/拖拽排序/側(cè)滑刪除/側(cè)滑選擇/萬能分割線》

RecyclerViewHelper:https://github.com/alidili/RecyclerViewHelper

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末率寡,一起剝皮案震驚了整個濱河市迫卢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冶共,老刑警劉巖乾蛤,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捅僵,居然都是意外死亡家卖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門庙楚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來上荡,“玉大人,你說我怎么就攤上這事馒闷±壹瘢” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵窜司,是天一觀的道長沛善。 經(jīng)常有香客問我,道長塞祈,這世上最難降的妖魔是什么金刁? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任帅涂,我火速辦了婚禮,結(jié)果婚禮上尤蛮,老公的妹妹穿的比我還像新娘媳友。我一直安慰自己,他們只是感情好产捞,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布醇锚。 她就那樣靜靜地躺著,像睡著了一般坯临。 火紅的嫁衣襯著肌膚如雪焊唬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天看靠,我揣著相機與錄音赶促,去河邊找鬼。 笑死挟炬,一個胖子當著我的面吹牛鸥滨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播谤祖,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼婿滓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了粥喜?” 一聲冷哼從身側(cè)響起凸主,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎容客,沒想到半個月后秕铛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡缩挑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年但两,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片供置。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡谨湘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出芥丧,到底是詐尸還是另有隱情紧阔,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布续担,位于F島的核電站擅耽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏物遇。R本人自食惡果不足惜乖仇,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一憾儒、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乃沙,春花似錦起趾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜀铲,卻和暖如春边琉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背记劝。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工艺骂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人隆夯。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像别伏,于是被迫代替她去往敵國和親蹄衷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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