Android Jetpack架構-Paging自定義上拉加載更多

在這裏插入圖片描述
在前面章節介紹了Jetpack中的Paging的基本使用,在閱讀本文前,若不知Paging的基本使用的朋友,可以查看筆者之前的文章Android Jetpack架構組件-Paging介紹及實踐

知道了Paging的基本使用,但並不滿足實際開發,雖然Paging可以實現分頁加載,但Paging在數據請求的時,只要有一次返回的數據爲空及PagedList爲空,則再不會進行分頁

這顯然是不友好的,因爲返回數據爲空有多種原因,可能是網絡或者查詢數據格式等,返回的PageList爲空,這個時候如果將分頁結束掉,則顯然不能接受;

或者Paging實現的分頁加載,如果滑動很快的話,則會出現加載明顯卡頓的效果,且無任何友好UI效果展示,如下圖所示:
卡頓加載更多.gif

在實際開發中,我們希望是慢慢滑動的時候,Paging幫我們處理分頁邏輯,而當快速滑動的時候,我們自己接管Paging的分頁加載邏輯,出現加載更多的loading,如下效果所示:

加載更多有自定義加載動畫

接下來,按照上面需求,實現當正常慢慢活動的時候,Paging幫我們分頁,當快速滑動的時候,則我們接管Paging的分頁加載,

示例以Jetpack中的
ViewModelDataSourcePagingPagingListAdapter並且配合SmartRefreshLayout來完成上拉加載和下拉刷新

  • 當然監聽RecycleView加載更多的視圖的有很多種方法,這裏直接使用SmartRefreshLayout

在開始之前,先通過ViewModel+DataSource+PagingListAdapter將數據綁定到RecycleView上,若看過之前的基本使用,則以下基本使用部分可以略過

Paging的基本使用

  • 1、先將Paging的基本使用及數據加載完成,則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中的實現
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);
        }
    }


    //簡單的請求網絡業務邏輯
    @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發送數據 告訴UI層 是否應該主動關閉上拉加載分頁的動畫
                    ((MutableLiveData) getBoundaryPageData()).postValue(responseArticle.getData().getDatas().size() > 0);
                    loadAfter.set(false);
                }
                mPageIndex = pageIndex + 1;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 3、通過PagedListAdapter將數據綁定到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的基本使用則已經完成,接下來,我們將實現手動接管Paging的上拉加載與下來刷新

實現上拉加載和下拉刷新

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

通過實現smartRefreshLayout的loadMore()中的實現邏輯,如下所示:

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

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

        //需要注意這裏,在PaingProViewModel中自實現loadAfter方法,實現請求分頁數據的邏輯
        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) {
                    //這裏 咱們手動接管 分頁數據加載的時候 使用MutableItemKeyedDataSource也是可以的。
                    //由於當且僅當 paging不再幫我們分頁的時候,我們纔會接管。所以 就不需要ViewModel中創建的DataSource繼續工作了,所以使用新的DataSource對象,這裏是MutablePageKeyedDataSource
                    MutablePageKeyedDataSource dataSource = new MutablePageKeyedDataSource();

                    //這裏要把列表上已經顯示的先添加到dataSource.data中
                    //而後把本次分頁回來的數據再添加到dataSource.data中
                    dataSource.data.addAll(currentList);
                    dataSource.data.addAll(data);

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

可以看到我們通過,在PaingProViewModel中定義loadAfter方法,實現接管Paging分頁加載的請求數據邏輯,

  • 4、實現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發送數據 告訴UI層 是否應該主動關閉上拉加載分頁的動畫
                                            ((MutableLiveData) getBoundaryPageData()).postValue(responseArticle.getData().getDatas().size() > 0);
                                            loadAfter.set(false);
                                        }
                                        mPageIndex = pageIndex + 1;
                                    }
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                );
    }

loadAfter爲設置是否是Paging上拉加載的標記位,只有Paging進行過上拉加載的時候,才接管上拉加載,即加載的頁碼大於0的時候才接管,否則返回空的PagedList即可。

  • 5、自定義的MutablePageKeyedDataSource的實現
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);
    }
}

作用相當於重新創建一個新的DataSource,且綁定數據集合構建出一個PagedList對象,供Paging使用。

總結

到這裏,Paging自定義上拉加載更多介紹完了,建檔總結,即通過SmartRefreshLayout監聽RecycleView的loadMore方法,通過在ViewModel中自定義loadAfter來加載數據,且重新創建DataSource和將集合數據List,和重新構建出一個PageList即可,文章中的示例代碼已上Jetpack/pagingpro

該倉庫爲演示Jetpack的組件的倉庫,分別對Lifecyele、LiveData、ViewModel、Room、WorkManager、Paging的介紹和使用

##詳細介紹文章

項目目錄結構爲如下

image.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章