Android Jetpack架構組件(九)之Paging

1、Paging簡介

在Android應用開發中,咱們常常須要以列表的方式來展現大量的數據,這些數據可能來自網路,也能夠來自本地的數據庫。爲了不一次性加載大量的數據,對數據進行分頁就顯得頗有必要。分頁加載能夠根據須要對數據進行按需加載,在不影響用戶體驗的前提下,提高應用的性能。html

爲了方便開發者進行分頁處理,Google爲開發者提供了分頁組件(Paging),藉助Paging組件開發者能夠輕鬆的加載和呈現大型數據集,同時在 RecyclerView 中進行快速、無限滾動。而且,它能夠從本地存儲和/或網絡加載分頁數據,並讓開發者可以定義內容的加載方式,同時它還支持與Room、LiveData 和 RxJava組合使用。java

1.1 支持的架構類型

目前,Paging能夠支持3種架構類型,分別是網路、數據、網路和數據庫,架構的示意圖以下所示。
在這裏插入圖片描述android

網路

在Android應用開發中,對網路數據進行分頁加載是一種比較常見的場景,也是咱們平時開發中遇到得最多的。不一樣公司對分頁機制所涉及的API接口一般會不同,但整體而言,能夠分爲3中。針對這些不一樣的分類,Paging提供了PageKeyedDataSource、PositionalDataSource和ItemKeyedDataSource。數據庫

  • PageKeyedDataSource:根據傳入的頁面num獲取某一頁的數據,好比獲取第2頁的數據。
  • PositionalDataSource:分頁時默認顯示的第幾頁。
  • ItemKeyedDataSource:請求下一頁的關鍵字。
數據庫

除了網路外,數據源來源於數據庫的場景也很是多,若是已經掌握了對網路數據的分頁,那麼對數據庫的數據進行分頁天然十分簡單,只不過數據源的讀取方式不一樣而已。json

網路+數據庫

在這種場景中,咱們會對網路的數據進行緩存,而數據庫就是比較場景的一種數據持久化方式,好比聊天應用中。首先,咱們會利用數據庫對網路數據進行緩存,不過在這種場景下,咱們須要同時處理數據庫和網路兩個數據源,所以須要約定好網路和數據庫的數據處理邏輯。api

1.2 工做流程

在正式使用Paging以前,咱們須要對Paging的工做流程有一個大體的瞭解。以下圖所示,是使用Paging須要經歷的幾個步驟。
在這裏插入圖片描述
如上圖所示,主要的步驟以下:緩存

  1. 使用DataSource從服務器獲取或者從本地數據庫獲取數據。
  2. 將數據保存到PageList中。
  3. 將PageList的數據交給PageListAdapter。
  4. PageListAdapter在後臺線程對比原來的PageList和新的PageList,生成新的PageList。
  5. PageListAdapter通知RecyclerView進行數據的更新。

1.3 核心概念

使用Paging庫進行分頁加載時,須要用到幾個核心的類,分別是PagedListAdapter、PageList和DataSource。服務器

PagedListAdapter

衆所周知,在Android列表開發中須要使用RecyclerView,而且須要配合自定義Adapter。PagedListAdapter繼承於RecyclerView.Adapter,這代表它也是一個RecyclerView.Adapter,而且擴展了RecyclerView.Adapter的支持異步差分更新功能。網絡

PageList

PageList是用於通知DataSource什麼時候獲取數據,以及如何獲取數據。好比,什麼時候獲取第一頁數據,以及什麼時候開始加載數據等待。而且,DataSource數據源都將經過PageList設置給PagedListAdapter。架構

DataSource

DataSource主要用於執行數據的加載操做,而且數據的載入須要在子線程中進行,不然會形成主線程的阻塞。DataSource的來源能夠是網路,也能夠是本地的數據庫,如Room。根據分頁機制的不一樣,DataSource能夠有3種來源,分別是PageKeyedDataSource、PositionalDataSource和ItemKeyedDataSource。

  • PageKeyedDataSource:根據傳入的頁面num獲取某一頁的數據,好比獲取第2頁的數據。
  • PositionalDataSource:分頁時默認顯示的第幾頁。
  • ItemKeyedDataSource:請求下一頁的關鍵字。

2、基本使用

2.1 添加依賴

首先,在app的build.gradle文件中添加Paging組件庫的依賴,以下所示。

dependencies {
   //網路請求庫
   implementation 'com.squareup.retrofit2:retrofit:2.9.0'
   implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
   implementation 'com.squareup.retrofit2:adapter-rxjava2:2.7.1'
   implementation 'com.google.code.gson:gson:2.8.6'
   implementation 'com.squareup.okhttp3:okhttp:4.8.0'
   implementation 'com.squareup.okio:okio:2.7.0'
   //Paging
   def paging_version = "2.1.0"
   implementation "androidx.paging:paging-runtime:$paging_version"
 }

2.2 定義網路請求

在Android開發中,數據一般來源於網路,咱們可使用retrofit完成網絡數據的請求。在獲取數據以前,咱們須要先新建一個數據實體類,主要用來存儲獲取的數據,以下所示是使用乾貨集中營的開源 Api 的數據的實體類。

public class DataBean {

    private int count;
    private boolean error;
    private List<ResultsBean> results;
    
    ...//省略get和set

    public static class ResultsBean {
        private String desc;
        private String ganhuo_id;
        private String publishedAt;
        private String readability;
        private String type;
        private String url;
        private String who;
       
        ... //省略get和set
    }
}

而後,爲了完成網路請求,咱們須要按照Retrofit的使用方式新建一個Api,用於統一管理請求接口,以下所示。

public interface Api {

    //開源API:http://gank.io/api/search/query/listview/category/Android/count/10/page/1
    @GET("api/search/query/listview/category/Android/count/10/page/{page}")
    Call<List<DataBean.ResultsBean>> getArticleList1(@Path("page") int page);

}

而後,咱們對Retrofit進行一個簡單的封裝,而後用它完成網路請求,以下所示。

public class RetrofitClient {

    private static RetrofitClient instance;
    private Retrofit mRetrofit;
    private OkHttpClient getClient(){
        OkHttpClient.Builder builder = new OkHttpClient().newBuilder()
                .connectTimeout(30, TimeUnit.SECONDS)//設置超時時間
                .readTimeout(10, TimeUnit.SECONDS)//設置讀取超時時間
                .writeTimeout(10, TimeUnit.SECONDS);//設置寫入超時時間
        return builder.build();
    }

    public RetrofitClient() {
        mRetrofit=new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create())
                .client(getClient())
                .build();

    }

    public static RetrofitClient getInstance() {
        if (instance==null){
            instance=new RetrofitClient();
        }
        return instance;
    }
    
    public <T> T createApi(Class<T> cls){
        T t=mRetrofit.create(cls);
        return t;
    }
}

2.3 建立數據源

若是使用的是網路數據,使用Paging進行分頁加載時須要自定義DataSource。前面說過,DataSource有3個抽象類,分別是PageKeyedDataSource、PositionalDataSource和ItemKeyedDataSource,所以實際使用時須要繼承他們。

因爲此處加載的是網絡數據,因此使用PageKeyedDataSource更合適,咱們新建一個繼承自PageKeyedDataSource的自定義DataSource,以下所示。

public class PagingDataSource extends PageKeyedDataSource<String, DataBean.ResultsBean> {

    private static final String TAG = PagingDataSource.class.getSimpleName();
    private int mPage = 1;

    @Override
    public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull final LoadInitialCallback<String, DataBean.ResultsBean> callback) {
        Api api = RetrofitClient.getInstance().createApi(Api.class);
        Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
        call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
            @Override
            public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
                if (response.isSuccessful() && response.code() == 200) {
                    List<DataBean.ResultsBean> data  = response.body();
                    callback.onResult(data, "before", "after");
                }

            }

            @Override
            public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
                Log.e(TAG, "--onFailure-->" + t.getMessage());
            }
        });
    }

    //加載上一頁
    @Override
    public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, DataBean.ResultsBean> callback) {
        Log.i(TAG, "--loadBefore-->" + params.key);
    }

    //加載下一頁
    @Override
    public void loadAfter(@NonNull final LoadParams<String> params, @NonNull final LoadCallback<String, DataBean.ResultsBean> callback) {
        mPage++;
        Api api = RetrofitClient.getInstance().createApi(Api.class);
        Call<List<DataBean.ResultsBean>> call = api.getArticleList1(mPage);
        call.enqueue(new Callback<List<DataBean.ResultsBean>>() {
            @Override
            public void onResponse(Call<List<DataBean.ResultsBean>> call, Response<List<DataBean.ResultsBean>> response) {
                if (response.isSuccessful() && response.code() == 200) {
                    List<DataBean.ResultsBean> data  = response.body();
                    callback.onResult(data, params.key);
                }
            }

            @Override
            public void onFailure(Call<List<DataBean.ResultsBean>> call, Throwable t) {
                Log.i(TAG, "--onFailure-->" + t.getMessage());
            }
        });
    }
}

在上面的代碼中,PageKeyedDataSource須要重寫三個方法。

  • loadInitial():第一次請求數據,即初始化狀態時請求數據。
  • loadBefore():請求上一頁數據,基本沒有用處。
  • loadAfter(): 請求下一頁數據。

DataSource建立好了,再建立一個DataSource.Factory,返回對應的DataSource實例,以下所示。

public class PagingDataSourceFactory extends DataSource.Factory<String, DataBean.ResultsBean> {

    @NonNull
    @Override
    public DataSource<String, DataBean.ResultsBean> create() {
        PagingDataSource dataSource = new PagingDataSource();
        return dataSource;
    }
}

2.4 配置分頁參數

在Jetpack的架構裏面,官方推薦每一個頁面持有一個ViewModel對象,以保證數據的正確性以及避免其餘的問題產生。

在在 ViewModel 中建立 PagedList.Config 並進行分頁參數配置,建立 DataSource 工廠對象,最終生成支持分頁的 LiveData 數據。要想建立LiveData,須要先建立一個LivePagedListBuilder,LivePagedListbuilder有設分頁數量和配置參數兩種方法,以下所示。

public class PagingViewModel extends ViewModel {

    private int pageSize = 20;
    //PagedList配置
    private PagedList.Config config = new PagedList.Config.Builder()
            .setInitialLoadSizeHint(pageSize)//設置首次加載的數量
            .setPageSize(pageSize)//設置每頁加載的數量
            .setPrefetchDistance(2)//設置距離每頁最後數據項來時預加載下一頁數據
            .setEnablePlaceholders(false)//設置是否啓用UI佔用符
            .build();

    //DataSource.Factory
    private DataSource.Factory<String,DataBean.ResultsBean> factory = new PagingDataSourceFactory();

    //LiveData
    private LiveData<PagedList<DataBean.ResultsBean>> mPagedList = new LivePagedListBuilder<>(factory, config)
            .build();

    public LiveData<PagedList<DataBean.ResultsBean>> getPagedList() {
        return mPagedList;
    }

}

上面代碼中,咱們提到了佔位符,佔位符的做用是在數據完成渲染以前,向用戶顯示的默認視圖效果。佔位符具備如下優勢:

  • 支持滾動條:PagedList 可向 PagedListAdapter 提供列表項數量。此信息容許適配器繪製滾動條來傳達整個列表大小。有新頁面載入時,滾動條不會跳到指定位置,由於列表不會改變大小。
  • 無需加載旋轉圖標:因爲列表大小已知,所以無需提醒用戶正在加載更多項。

不過,在添加對佔位符的支持以前,請注意如下前提條件:

  • 須要可計數的數據集:Room 持久性庫 中的 DataSource 實例能夠有效地計算項的數量。但若是您使用的是自定義本地存儲解決方案或網絡專用數據架構,肯定數據集包含多少項可能會開銷極大,甚至根本沒法肯定。
  • 適配器必須考慮未加載的項:爲準備列表以應對增加而使用的適配器或呈現機制須要處理 Null 列表項。例如,將數據綁定到 ViewHolder 時,您須要提供默認值來表示未加載數據。
  • 須要一樣大小的項視圖:若是列表項大小會隨着內容而變(例如社交網絡更新),則項之間的交叉漸變效果並不理想。在這種狀況下,咱們強烈建議停用佔位符。

2.5 建立PagedListAdapter

PagedListAdapter是一個特殊的RecyclerView的RecyclerAdapter,使用方法也和RecyclerAdapter的使用方式相似,以下所示。

public class PagingAdapter extends PagedListAdapter<DataBean.ResultsBean, PagingAdapter.ViewHolder> {

    public PagingAdapter() {
        super(itemCallback);
    }

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

    @Override
    public void onBindViewHolder(@NonNull PagingAdapter.ViewHolder holder, int position) {
        DataBean.ResultsBean bean = getItem(position);
        if (bean != null) {
            holder.desc.setText(bean.getDesc());
            holder.time.setText(bean.getPublishedAt());
            holder.type.setText(bean.getType());
            holder.auth.setText(bean.getWho());
        }
    }


    public class ViewHolder extends RecyclerView.ViewHolder{
        TextView desc;
        TextView time;
        TextView type;
        TextView auth;

        public ViewHolder(View itemView) {
            super(itemView);
            desc = itemView.findViewById(R.id.desc);
            time = itemView.findViewById(R.id.time);
            type = itemView.findViewById(R.id.type);
            auth = itemView.findViewById(R.id.auth);
        }
    }


    private static DiffUtil.ItemCallback<DataBean.ResultsBean> itemCallback = new DiffUtil.ItemCallback<DataBean.ResultsBean>() {
        @Override
        public boolean areItemsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
            return oldItem.getGanhuo_id().equals(newItem.getGanhuo_id());
        }

        @SuppressLint("DiffUtilEquals")
        @Override
        public boolean areContentsTheSame(@NonNull DataBean.ResultsBean oldItem, @NonNull DataBean.ResultsBean newItem) {
            return oldItem.equals(newItem);
        }
    };

}

在使用PagedListAdapter時,PagedListAdapter內部默認實現DiffUtil來進行數據的差量計算,因此咱們在構造方法裏面傳遞一個DiffUtil.ItemCallback。

2.6 加載分頁數據

通過上面的處理後,接下來只須要在Activity中進行數據的請求和綁定便可,以下所示。

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";
    private ActivityMainBinding activityMainBinding;
    private PagingViewModel mViewModel;
    private PagingAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        initRecyclerView();
//        getData();
    }

    private void initRecyclerView() {
        adapter = new PagingAdapter();

        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
        activityMainBinding.recycle.setLayoutManager(layoutManager);
        activityMainBinding.recycle.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
        activityMainBinding.recycle.setAdapter(adapter);

        ViewModelProvider mViewModelProvider = new ViewModelProvider(this,
                new ViewModelProvider.AndroidViewModelFactory(getApplication()));
        mViewModel = mViewModelProvider.get(PagingViewModel.class);

    }

    public void getData() {
        mViewModel.getPagedList().observe(this, new Observer<PagedList<DataBean.ResultsBean>>() {
            @Override
            public void onChanged(PagedList<DataBean.ResultsBean> dataBeans) {
                adapter.submitList(dataBeans);
            }
        });
    }
}

咱們使用 LiveData 監聽加載的數據,而後使用 sumbitList 將數據提交給 PagedListAdapter,PagedListAdapter會在後臺線程中對比新舊數據的差別,最後更新 RecyclerView。

3、Paging3

3.1 概述

Paging是JetPack框架提供的一個分頁庫,它能夠幫助開發者從本地存儲或經過網絡加載顯示數據,不過因爲歷史緣由,早期的Paging存在各類使用上的問題,所以Android在後面提供了Paging3用來替換早期的Paging2。相比Paging2,Paging3有以下一些優勢。

  • 在內存中緩存分頁數據,確保 App 在使用分頁數據時有效地使用系統資源。
  • 內置刪除重複數據的請求,確保 App 有效地使用網絡帶寬和系統資源。
  • 可配置 RecyclerView 的 Adapters,當用戶滾動到加載數據的末尾時自動請求數據。
  • 支持 Kotlin 協程和 Flow, 以及 LiveData 和 RxJava。
  • 內置的錯誤處理支持,包括刷新和重試等功能。

3.1.1 主要變化

在 Paging3 以前,Paging提供了 ItemKeyedDataSource、PageKeyedDataSource、PositionalDataSource 這三個類來進行數據獲取的操做。

  • PositionalDataSource:用於加載數據有限的數據,好比加載本地數據庫。
  • PageKeyedDataSource:用來請求網絡數據,適用於經過頁碼分頁來請求數據。
  • ItemKeyedDataSource:用來請求網絡數據,它適用於經過當前頁面最後一條數據的 id做爲下一頁的數據的開始的位置的場景。

在 Paging3 以後,ItemKeyedDataSource、PageKeyedDataSource、PositionalDataSource 合併爲一個 PagingSource,全部舊 API 加載方法被合併到 PagingSource 中的單個 load() 方法中,以下所示。

abstract suspend fun load(params: LoadParams<Key>): LoadResult<Key, Value>

除此以外,變化的內容還包括:

  • LivePagedListBuilder 和 RxPagedListBuilder 合併爲了 Pager。
  • 使用 PagedList.Config 替換 PagingConfig。
  • 使用 RemoteMediator 替換了 PagedList.BoundaryCallback 去加載網絡和本地數據庫的數據。

3.1.2 重要概念

在正式學習Paging3以前,咱們須要弄清楚幾個重要的概念:

  • PagingSource:單一的數據源,能夠表示數據庫請求獲得的數據,也能夠是網絡請求獲得的數據。
  • RemoteMediator:單一的數據源,它會在 PagingSource 沒有數據的時候,再使用 RemoteMediator 提供的數據,若是既存在數據庫請求又存在網絡請求,一般 PagingSource 用於進行數據庫請求,RemoteMediator 進行網絡請求。
  • PagingData:單次分頁數據的容器。
  • Pager:用來構建 Flow<PagingData> 的類,實現數據加載完成的回調。
  • PagingDataAdapter:分頁加載數據的 RecyclerView 的適配器。

總的來講,使用Paging3加載網絡數據的流程是:PagingSource 和 RemoteMediator 充當數據源的角色,ViewModel 使用 Pager 中提供的 Flow<PagingData> 監聽數據刷新,每當 RecyclerView 即將滾動到底部的時候,就會加載新的數據,最後再使用PagingAdapter 展現數據。

3.1.3 Paging3應用架構

下面是Android官方推薦的接入 Paging3的應用架構圖。
在這裏插入圖片描述
能夠發現,使用Paging3實現數據分頁時主要包含3個對象:

#### 數據倉庫層Repository

Repository層主要使用PagingSource這個分頁組件來實現,每一個PagingSource對象都對應一個數據源,以及該如何從該數據源中查找數據,PagingSource能夠從任何單個數據源好比網絡或者數據庫中查找數據。

Repository層還有另外一個分頁組件可使用RemoteMediator,它是一個分層數據源,好比有本地數據庫緩存的網絡數據源。

ViewModel層

Repository最終返回一個異步流包裹的PagingDataFlow<PagingData<Value>>,PagingData存儲了數據結果,最終可使用它將數據跟UI界面關聯起來。ViewModel通常都使用LiveData來跟UI層交互,Flow的擴展函數能夠直接轉換成一個LiveData可觀察對象。

UI層

UI層其實就是Activity/Fragment等視圖層,主要的做用是給RecycleView設置Adapter,給Adater設置數據。

3.2 基本使用

3.2.1, 添加依賴

首先,在app的build.gradle文件中添加Paging3組件庫的依賴,以下所示。

dependencies {
   ...
   //Paging3
   def paging_version = "3.0.0-alpha11"
   implementation "androidx.paging:paging-runtime:$paging_version"
 }

3.2.2 定義數據源

Paging 2提供了三種類型的 PageSource,開發者須要根據使用場景去進行選擇。而Paging 3對數據源進行了統一處理,開發時只須要繼承 PagingSource 便可。

Paging 3的數據源能夠是PagingSource,也能夠是RemoteMediator,它們的區別以下。

  • PagingSource:單一數據源以及如何從該數據源中查找數據,數據源的變更會直接映射到 UI 上。
  • RemoteMediator:實現加載網絡分頁數據並更新到數據庫中,可是數據源的變更不能直接映射到 UI 上。

那實際使用時,如何進行選擇呢?PagingSource主要用於加載有限的數據集,而RemoteMediator則主要用來加載網絡分頁數據,實際使用時須要結合 PagingSource 實現保存更多數據操做並映射到 UI 上。

下面以WanAndroid的接口爲例,接口地址爲:https://www.wanandroid.com/article/list/1/json,數據源的代碼以下。

public class Paging3DataSource extends PagingSource<Integer, ArticleBean.DataBean.DatasBean>  {


    @Nullable
    @Override
    public Object load(@NotNull LoadParams<Integer> params, @NotNull Continuation<? super LoadResult<Integer, ArticleBean.DataBean.DatasBean>> continuation) {
        int page = 0;
        if(params.getKey()!=null){
            page=params.getKey();
        }
        //獲取網絡數據
        ArticleBean result = (ArticleBean) RetrofitClient.getInstance().getApi().getArticleList(page);
        //須要加載的數據
        List<ArticleBean.DataBean.DatasBean> datas= result.getData().getDatas();
        //若是能夠往上加載更多就設置該參數,不然不設置
        String prevKey=null;
        //加載下一頁的key 若是傳null就說明到底了
        String nextKey=null;
        if(result.getData().getCurPage() == result.getData().getPageCount()){
            nextKey=null;
        }else {
            nextKey=String.valueOf(page+1);
        }
        return new LoadResult.Page<>(datas,prevKey,nextKey);
    }


}

在上面的代碼中,自定義的PagingSource須要繼承自PagingSource,須要傳入兩個泛型,第一個表示下一頁數據的加載方式,另外一個是返回數據的實體類。同時,自定義的PagingSource還須要重寫load方法來觸發異步加載,能夠看到它是一個用suspend修飾的掛起函數,能夠很方便的使用協程異步加載。

而load方法的參數LoadParams中有一個key值,能夠在加載下一頁數據時使用。返回值是一個LoadResult,出現異常調用LoadResult.Error(e),正常強開狀況下調用LoadResult.Page方法來設置從網絡或者數據庫獲取到的數據。

3.2.3 網絡請求

在實際應用開發中,咱們須要從網絡中獲取數據,而後再進行其餘業務操做。網絡請求通常會藉助Retrofit來實現,下面是使用Retrofit完成WanAndroid接口請求的簡單的封裝,代碼以下。

public class RetrofitClient {

    private static String BASE_URL="https://www.wanandroid.com/";
    private static RetrofitClient instance;
    private Retrofit mRetrofit;

    private OkHttpClient getClient(){
        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                Log.i("RetrofitClient: ", message);
            }
        });
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(interceptor)
                .connectTimeout(60, TimeUnit.SECONDS)
                .build();
        return client;
    }

    public RetrofitClient() {
        mRetrofit=new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create())
                .client(getClient())
                .build();
    }

    public static RetrofitClient getInstance() {
        if (instance==null){
            instance=new RetrofitClient();
        }
        return instance;
    }

    public <T> T createApi(Class<T> cls){
        T t=mRetrofit.create(cls);
        return t;
    }

}

而後,在建立一個WanAndroidApi來管理全部的Api接口,以下所示。

public interface WanAndroidApi {

    @GET("article/list/{pageNum}/json")
    Call<ArticleBean> getArticleList(@Path("page") int page);
}

3.2.4 構建ViewModel

分頁數據的容器被稱爲 PagingData,每次刷新數據時,都會建立一個 PagingData 的實例。若是要建立 PagingData 數據流,那麼須要建立一個 Pager 實例,並提供一個 PagingConfig 配置對象和一個能夠告訴 Pager 如何獲取您實現的 PagerSource 的實例的函數,以供 Pager 使用。

實際開發中,Repository返回的是一個異步流包裹的PagingDataFlow<PagingData<Value>>,PagingData存儲了數據結果。而在MVVM中,咱們須要構建ViewModel來實現是LiveData和UI層交互,而ViewModel的Flow的擴展函數能夠將直接將PagingSource轉換成一個LiveData可觀察對象,代碼以下。

public class Paging3ViewModel extends ViewModel {

    PagingConfig pagingConfig = new PagingConfig(20, 3);

    public LiveData<PagingData<ArticleBean.DataBean.DatasBean>> getArticleData() {
        CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(this);
        Pager<Integer, ArticleBean.DataBean.DatasBean> pager = new Pager<>(pagingConfig, () -> new Paging3DataSource());
        LiveData<PagingData<ArticleBean.DataBean.DatasBean>> cachedResult=PagingLiveData.cachedIn(PagingLiveData.getLiveData(pager), viewModelScope);
        return cachedResult;
    }

}

3.2.5 建立Adapter

和Paging2的Adapter使用步驟相似,在Paging3中建立Adapter須要繼承 PagingDataAdapter,而後提供 DiffUtil.ItemCallback<T>,以下所示。

public class Paging3Adapter extends PagingDataAdapter<ArticleBean.DataBean.DatasBean, Paging3Adapter.ViewHolder> {


    public Paging3Adapter(@NotNull DiffUtil.ItemCallback<ArticleBean.DataBean.DatasBean> diffCallback) {
        super(itemCallback);
    }

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

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        ArticleBean.DataBean.DatasBean bean = getItem(position);
        if (bean != null) {
            holder.desc.setText(bean.getDesc());
            holder.time.setText(String.valueOf(bean.getPublishTime()));
            holder.type.setText(bean.getType());
            holder.auth.setText(bean.getAuthor());
        }
    }

    public static class ViewHolder extends RecyclerView.ViewHolder{
        TextView desc;
        TextView time;
        TextView type;
        TextView auth;

        public ViewHolder(View itemView) {
            super(itemView);
            desc = itemView.findViewById(R.id.desc);
            time = itemView.findViewById(R.id.time);
            type = itemView.findViewById(R.id.type);
            auth = itemView.findViewById(R.id.auth);
        }
    }


    public static DiffUtil.ItemCallback<ArticleBean.DataBean.DatasBean> itemCallback = new DiffUtil.ItemCallback<ArticleBean.DataBean.DatasBean>() {
        @Override
        public boolean areItemsTheSame(@NonNull ArticleBean.DataBean.DatasBean oldItem, @NonNull ArticleBean.DataBean.DatasBean newItem) {
            return oldItem.getId()==newItem.getId();
        }

        @SuppressLint("DiffUtilEquals")
        @Override
        public boolean areContentsTheSame(@NonNull ArticleBean.DataBean.DatasBean oldItem, @NonNull ArticleBean.DataBean.DatasBean newItem) {
            return oldItem.equals(newItem);
        }
    };
}

能夠發現,Paging3Adapter的代碼和普通的Adapter的代碼是差很少的,也是須要重寫onCreateViewHolder和onBindViewHolder兩個方法。

3.2.6 在UI 中展現數據

最後,咱們在Activity中使用RecyclerView展現獲取的數據便可,以下所示。

public class MainActivity extends AppCompatActivity {

    private final String TAG = "MainActivity";
    private Paging3Adapter adapter;

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

    private void initRecyclerView() {
        adapter = new Paging3Adapter();
        RecyclerView  recyclerView = findViewById(R.id.recycle);
        recyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));
        recyclerView.setAdapter(adapter);

        Paging3ViewModel viewModel=new ViewModelProvider(this).get(Paging3ViewModel.class);

        viewModel.getArticleData().observe(this, pagingData -> adapter.submitData(getLifecycle(),pagingData));
    }
}

除此以外,Paging3還支持添加 Header 和 Footer來實現上拉刷新和下拉加載更多的功能。

參考: 使用官方Paging3分頁庫實現RecyclerView加載更多

相關文章
相關標籤/搜索