Android Jetpack架構(gòu)組件-Paging自定義上拉加載更多

在前面章節(jié)介紹了Jetpack中的Paging的基本使用稻薇,在閱讀本文前恒傻,若不知Paging的基本使用的朋友余素,可以查看筆者之前的文章Android Jetpack架構(gòu)組件-Paging介紹及實(shí)踐

知道了Paging的基本使用蒙兰,但并不滿足實(shí)際開發(fā)闻鉴,雖然Paging可以實(shí)現(xiàn)分頁(yè)加載茵乱,但Paging在數(shù)據(jù)請(qǐng)求的時(shí),只要有一次返回的數(shù)據(jù)為空及PagedList為空孟岛,則再不會(huì)進(jìn)行分頁(yè)

這顯然是不友好的瓶竭,因?yàn)榉祷財(cái)?shù)據(jù)為空有多種原因,可能是網(wǎng)絡(luò)或者查詢數(shù)據(jù)格式等蚀苛,返回的PageList為空在验,這個(gè)時(shí)候如果將分頁(yè)結(jié)束掉,則顯然不能接受堵未;

或者Paging實(shí)現(xiàn)的分頁(yè)加載腋舌,如果滑動(dòng)很快的話,則會(huì)出現(xiàn)加載明顯卡頓的效果渗蟹,且無任何友好UI效果展示块饺,如下圖所示:


卡頓加載更多.gif

在實(shí)際開發(fā)中赞辩,我們希望是慢慢滑動(dòng)的時(shí)候,Paging幫我們處理分頁(yè)邏輯授艰,而當(dāng)快速滑動(dòng)的時(shí)候辨嗽,我們自己接管Paging的分頁(yè)加載邏輯,出現(xiàn)加載更多的loading淮腾,如下效果所示:

加載更多有自定義加載動(dòng)畫

接下來糟需,按照上面需求,實(shí)現(xiàn)當(dāng)正常慢慢活動(dòng)的時(shí)候谷朝,Paging幫我們分頁(yè)洲押,當(dāng)快速滑動(dòng)的時(shí)候,則我們接管Paging的分頁(yè)加載圆凰,

示例以Jetpack中的
ViewModel杈帐、DataSourcePaging专钉、PagingListAdapter并且配合SmartRefreshLayout來完成上拉加載和下拉刷新

  • 當(dāng)然監(jiān)聽RecycleView加載更多的視圖的有很多種方法挑童,這里直接使用SmartRefreshLayout

在開始之前,先通過ViewModel+DataSource+PagingListAdapter將數(shù)據(jù)綁定到RecycleView上跃须,若看過之前的基本使用站叼,則以下基本使用部分可以略過

Paging的基本使用

  • 1、先將Paging的基本使用及數(shù)據(jù)加載完成,則Activity中的代碼如下所示:
package com.onexzgj.inspur.pageingsample.pagingpro;

public class PagingProActivity extends AppCompatActivity implements OnRefreshListener, OnLoadMoreListener {

    @SuppressLint("RestrictedApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_paging_pro);

        recyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));

        adapter = new PagingProAdapter(this);
        recyclerView.setAdapter(adapter);

        paingProViewModel = new ViewModelProvider.NewInstanceFactory().create(PaingProViewModel.class);

        paingProViewModel.getPageData().observe(this, new Observer<PagedList<ResponseArticle.DataBean.Article>>() {
            @Override
            public void onChanged(PagedList<ResponseArticle.DataBean.Article> articles) {
                submitList(articles);
            }
        });
    }

    public void submitList(PagedList<ResponseArticle.DataBean.Article> result) {
        if (result.size() > 0) {
            adapter.submitList(result);
        }
    }
}
  • 2菇民、再來看看PaingProViewModel中的實(shí)現(xiàn)
package com.onexzgj.inspur.pageingsample.pagingpro;
/**
 * author:onexzgj
 * time:2020/5/4
 */
public class PaingProViewModel extends AbsPagingProViewModel<ResponseArticle.DataBean.Article> {
    private AtomicBoolean loadAfter = new AtomicBoolean(false);
    private int mPageIndex = 0;

    public int getmPageIndex() {
        return mPageIndex;
    }

    @Override
    protected DataSource createDataSource() {
        return new ArticleDataSource();
    }


    class ArticleDataSource extends PageKeyedDataSource<Integer, ResponseArticle.DataBean.Article> {

        @Override
        public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, ResponseArticle.DataBean.Article> callback) {
            loadData(0, callback, null);
        }

        @Override
        public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, ResponseArticle.DataBean.Article> callback) {
            callback.onResult(Collections.emptyList(), 0);
        }

        @Override
        public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, ResponseArticle.DataBean.Article> callback) {
            loadData(params.key, null, callback);
        }
    }


    //簡(jiǎn)單的請(qǐng)求網(wǎng)絡(luò)業(yè)務(wù)邏輯
    @SuppressLint("RestrictedApi")
    private void loadData(int pageIndex, PageKeyedDataSource.LoadInitialCallback<Integer, ResponseArticle.DataBean.Article> initCallback, PageKeyedDataSource.LoadCallback<Integer, ResponseArticle.DataBean.Article> callback) {

        mPageIndex = pageIndex;
        if (pageIndex > 0) {
            loadAfter.set(true);
        }

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("https://www.wanandroid.com/article/list/" + pageIndex + "/json").build();
        try {
            Response response = null;
            response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                ResponseArticle responseArticle = JSON.parseObject(response.body().string(), ResponseArticle.class);

                if (initCallback != null) {
                    initCallback.onResult(responseArticle.getData().getDatas(), pageIndex - 1, pageIndex + 1);
                } else {
                    callback.onResult(responseArticle.getData().getDatas(), pageIndex + 1);
                }

                if (pageIndex > 0) {
                    //通過BoundaryPageData發(fā)送數(shù)據(jù) 告訴UI層 是否應(yīng)該主動(dòng)關(guān)閉上拉加載分頁(yè)的動(dòng)畫
                    ((MutableLiveData) getBoundaryPageData()).postValue(responseArticle.getData().getDatas().size() > 0);
                    loadAfter.set(false);
                }
                mPageIndex = pageIndex + 1;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 3大年、通過PagedListAdapter將數(shù)據(jù)綁定到RecycleView上
import com.onexzgj.inspur.pageingsample.R;
/**
 * author:onexzgj
 * time:2020/5/4
 */
public class PagingProAdapter extends PagedListAdapter<ResponseArticle.DataBean.Article, PagingProAdapter.ViewHolder> {
    public Context mContext;

    protected PagingProAdapter(Context context) {
        super(new DiffUtil.ItemCallback<ResponseArticle.DataBean.Article>() {

            @Override
            public boolean areItemsTheSame(@NonNull ResponseArticle.DataBean.Article oldItem, @NonNull ResponseArticle.DataBean.Article newItem) {
                return oldItem == newItem;
            }

            @Override
            public boolean areContentsTheSame(@NonNull ResponseArticle.DataBean.Article oldItem, @NonNull ResponseArticle.DataBean.Article newItem) {
                return oldItem.getId() == newItem.getId();
            }
        });
        this.mContext= context;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.bindData(getItem(position));
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        private TextView nameView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            nameView = itemView.findViewById(R.id.tv_info);
        }

        public void bindData(ResponseArticle.DataBean.Article item) {
            nameView.setText(item.getTitle());
        }
    }
}

到這里Paging的基本使用則已經(jīng)完成,接下來玉雾,我們將實(shí)現(xiàn)手動(dòng)接管Paging的上拉加載與下來刷新

實(shí)現(xiàn)上拉加載和下拉刷新

  • 1翔试、通過SmartRefreshLayout,來監(jiān)聽RecycleView的下拉刷新與上拉加載更多的監(jiān)聽,如何使用SmartRefreshLayout這里不做詳述
        ...
        smartRefreshLayout.setEnableRefresh(true);
        smartRefreshLayout.setEnableLoadMore(true);
        smartRefreshLayout.setOnRefreshListener(this);
        smartRefreshLayout.setOnLoadMoreListener(this);
        ...
  • 2复旬、刷新邏輯實(shí)現(xiàn)
    通過實(shí)現(xiàn)smartRefreshLayout的onRefresh(),將DataSource重新初始化一下即可垦缅,即如下所示:
    @Override
    public void onRefresh(@NonNull RefreshLayout refreshLayout) {
        paingProViewModel.getDataSource().invalidate();
    }
  • 3、 加載更多邏輯實(shí)現(xiàn)

通過實(shí)現(xiàn)smartRefreshLayout的loadMore()中的實(shí)現(xiàn)邏輯驹碍,如下所示:

    @Override
    public void onLoadMore(@NonNull RefreshLayout refreshLayout) {

        //若列表數(shù)據(jù)為空壁涎,則不觸發(fā)上拉加載更多數(shù)據(jù)
        final PagedList<ResponseArticle.DataBean.Article> currentList = adapter.getCurrentList();
        if (currentList == null || currentList.size() <= 0) {
            finishRefresh(false);
            return;
        }

        //需要注意這里,在PaingProViewModel中自實(shí)現(xiàn)loadAfter方法志秃,實(shí)現(xiàn)請(qǐng)求分頁(yè)數(shù)據(jù)的邏輯
        paingProViewModel.loadAfter(paingProViewModel.getmPageIndex(),new PageKeyedDataSource.LoadCallback<Integer, ResponseArticle.DataBean.Article>(){

            @Override
            public void onResult(@NonNull List<ResponseArticle.DataBean.Article> data, @Nullable Integer adjacentPageKey) {
                PagedList.Config config = currentList.getConfig();
                if (data != null && data.size() > 0) {
                    //這里 咱們手動(dòng)接管 分頁(yè)數(shù)據(jù)加載的時(shí)候 使用MutableItemKeyedDataSource也是可以的怔球。
                    //由于當(dāng)且僅當(dāng) paging不再幫我們分頁(yè)的時(shí)候,我們才會(huì)接管浮还。所以 就不需要ViewModel中創(chuàng)建的DataSource繼續(xù)工作了竟坛,所以使用新的DataSource對(duì)象,這里是MutablePageKeyedDataSource
                    MutablePageKeyedDataSource dataSource = new MutablePageKeyedDataSource();

                    //這里要把列表上已經(jīng)顯示的先添加到dataSource.data中
                    //而后把本次分頁(yè)回來的數(shù)據(jù)再添加到dataSource.data中
                    dataSource.data.addAll(currentList);
                    dataSource.data.addAll(data);

                    PagedList pagedList = dataSource.buildNewPagedList(config);
                    submitList(pagedList);
                }
            }
        });
    }

可以看到我們通過,在PaingProViewModel中定義loadAfter方法担汤,實(shí)現(xiàn)接管Paging分頁(yè)加載的請(qǐng)求數(shù)據(jù)邏輯涎跨,

  • 4、實(shí)現(xiàn)PaingProViewModel中的自定義的方法loadAfter()
    @SuppressLint("RestrictedApi")
    public void loadAfter(int pageIndex, PageKeyedDataSource.LoadCallback<Integer, ResponseArticle.DataBean.Article> callback) {

        Log.d("TAG", "loadAfter: pageIndex" + pageIndex);
        //是否加載更多的表示位
        if (loadAfter.get()) {
            callback.onResult(Collections.emptyList(), 0);
            return;
        }

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("https://www.wanandroid.com/article/list/" + pageIndex + "/json").build();
        ArchTaskExecutor.getIOThreadExecutor().
                execute(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Response response = null;
                                    response = client.newCall(request).execute();
                                    if (response.isSuccessful()) {
                                        ResponseArticle responseArticle = JSON.parseObject(response.body().string(), ResponseArticle.class);
                                        callback.onResult(responseArticle.getData().getDatas(), pageIndex + 1);

                                        if (pageIndex > 0) {
                                            //通過BoundaryPageData發(fā)送數(shù)據(jù) 告訴UI層 是否應(yīng)該主動(dòng)關(guān)閉上拉加載分頁(yè)的動(dòng)畫
                                            ((MutableLiveData) getBoundaryPageData()).postValue(responseArticle.getData().getDatas().size() > 0);
                                            loadAfter.set(false);
                                        }
                                        mPageIndex = pageIndex + 1;
                                    }
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                );
    }

loadAfter為設(shè)置是否是Paging上拉加載的標(biāo)記位崭歧,只有Paging進(jìn)行過上拉加載的時(shí)候隅很,才接管上拉加載,即加載的頁(yè)碼大于0的時(shí)候才接管率碾,否則返回空的PagedList即可叔营。

  • 5、自定義的MutablePageKeyedDataSource的實(shí)現(xiàn)
package com.onexzgj.inspur.pageingsample.pagingpro;

@SuppressLint("RestrictedApi")
public class MutablePageKeyedDataSource<Value> extends PageKeyedDataSource<Integer, Value> {
    public List<Value> data = new ArrayList<>();

    public PagedList<Value> buildNewPagedList(PagedList.Config config) {
      PagedList<Value> pagedList = new PagedList.Builder<Integer, Value>(this, config)
                .setFetchExecutor(ArchTaskExecutor.getIOThreadExecutor())
                .setNotifyExecutor(ArchTaskExecutor.getMainThreadExecutor())
                .build();

        return pagedList;
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Value> callback) {
        callback.onResult(data, null, null);
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Value> callback) {
        callback.onResult(Collections.emptyList(), null);
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Value> callback) {
        callback.onResult(Collections.emptyList(), null);
    }
}

作用相當(dāng)于重新創(chuàng)建一個(gè)新的DataSource所宰,且綁定數(shù)據(jù)集合構(gòu)建出一個(gè)PagedList對(duì)象审编,供Paging使用。

總結(jié)

到這里歧匈,Paging自定義上拉加載更多介紹完了,建檔總結(jié)砰嘁,即通過SmartRefreshLayout監(jiān)聽RecycleView的loadMore方法件炉,通過在ViewModel中自定義loadAfter來加載數(shù)據(jù),且重新創(chuàng)建DataSource和將集合數(shù)據(jù)List矮湘,和重新構(gòu)建出一個(gè)PageList即可斟冕,文章中的示例代碼已上Jetpack/pagingpro

該倉(cāng)庫(kù)為演示Jetpack的組件的倉(cāng)庫(kù),分別對(duì)Lifecyele缅阳、LiveData磕蛇、ViewModel、Room十办、WorkManager秀撇、Paging的介紹和使用

詳細(xì)介紹文章

項(xiàng)目目錄結(jié)構(gòu)為如下

image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市向族,隨后出現(xiàn)的幾起案子呵燕,更是在濱河造成了極大的恐慌,老刑警劉巖件相,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件再扭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡夜矗,警方通過查閱死者的電腦和手機(jī)泛范,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來紊撕,“玉大人罢荡,你說我怎么就攤上這事。” “怎么了柠傍?”我有些...
    開封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵麸俘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我惧笛,道長(zhǎng)从媚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任患整,我火速辦了婚禮拜效,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘各谚。我一直安慰自己紧憾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開白布昌渤。 她就那樣靜靜地躺著赴穗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪膀息。 梳的紋絲不亂的頭發(fā)上般眉,一...
    開封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音潜支,去河邊找鬼甸赃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛冗酿,可吹牛的內(nèi)容都是我干的埠对。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼裁替,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼项玛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起弱判,我...
    開封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤稍计,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后裕循,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體臣嚣,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年剥哑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了硅则。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡株婴,死狀恐怖怎虫,靈堂內(nèi)的尸體忽然破棺而出暑认,到底是詐尸還是另有隱情,我是刑警寧澤大审,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布蘸际,位于F島的核電站,受9級(jí)特大地震影響徒扶,放射性物質(zhì)發(fā)生泄漏粮彤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一姜骡、第九天 我趴在偏房一處隱蔽的房頂上張望导坟。 院中可真熱鬧,春花似錦圈澈、人聲如沸惫周。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)递递。三九已至,卻和暖如春啥么,著一層夾襖步出監(jiān)牢的瞬間登舞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工饥臂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人似踱。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓隅熙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親核芽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子囚戚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359