Android分頁元件Paging簡單使用
title: Android分頁元件Paging簡單使用
date: 2018-10-10 17:03:23
tags: Paging
1.簡介:
Paging元件是Google新推出的分頁元件,可以輕鬆幫助開發者實現RecyclerView中分頁載入功能。
本文先開坑,等以後用到在詳細寫明。
推薦部落格: ofollow,noindex">https://www.jianshu.com/p/1bfec9b9612c
2.Paging庫引入:
implementation 'android.arch.paging:runtime:1.0.0'
剛引入就遇到了錯誤,真是一個不友好的開始:
Error:Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'. > java.lang.RuntimeException: java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex
一查資料,是匯入重複的包,這個出錯的地方還是google自己的東西,這豈不是
“大水衝了龍王廟,自家人打自家人”?
compile 'com.android.support:design:26.1.0'
google了一下,在stackoverflow中找到了解決辦法:
即用編譯版本改成27,並且把對應的庫版本也改為27
implementation 'com.android.support:appcompat-v7:27.1.1'
compile 'com.android.support:design:27.1.1'
問題了解決了,接下來我們看看他的工作原理
3.工作原理

PagingWork.gif
這是官方提供的原理圖,寫的很清楚從DataSource到PagedList, PagedListAdapter最後是我們的recyclerview,我們先眼熟這幾個名字,下文會常常出現。
3.1 先來看Adapter
public class AdapterPaging extends PagedListAdapter<VideoInfo, AdapterPaging.ViewHolder> { private Context mContext; public static final DiffUtil.ItemCallback<VideoInfo> mDiffCallback = new AdapterPaging.VideoInfoItemCallback(); protected AdapterPaging() { super(mDiffCallback); } @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { return null; } @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { VideoInfo videoInfo = getItem(position); } static class ViewHolder extends RecyclerView.ViewHolder{ private LinearLayout ll_videoInfo; public ViewHolder(View itemView) { super(itemView); ll_videoInfo = itemView.findViewById(R.id.ll_video_info); } } private static class VideoInfoItemCallback extends DiffUtil.ItemCallback<VideoInfo>{ @Override public boolean areItemsTheSame(VideoInfo oldItem, VideoInfo newItem) { return oldItem.getUrl() == newItem.getUrl(); } @Override public boolean areContentsTheSame(VideoInfo oldItem, VideoInfo newItem) { return (oldItem == newItem); } } }
大致的4個不同如下:
- Adapter不再繼承自RecyclerView.Adapter,改為繼承自PagedListAdapter,因為PagedListAdapter就是RecyclerView.Adapter的一個子類。
- 定義內部回撥介面VideoInfoItemCallback繼承自DiffUtil.ItemCallback<VideoInfo>,並且例項化一個父類引用指向子類(VideoInfoItemCallback)物件
public static final DiffUtil.ItemCallback<VideoInfo> mDiffCallback = new AdapterPaging.VideoInfoItemCallback();
- 重寫構造方法,無需引數傳入,呼叫父類構造方法將mDiffCallback傳入。
- 通onBindViewHolder中過呼叫getItem(position);獲得指定位置的資料物件。
因為Adapter中不再需要維護一個數據List了,PagedListAdapter中已經維護有,並且提供getItem()方法訪問。
3.2 在Activity中的使用
在使用Paging後,我們無需向Adapter中在傳入資料來源List,我們需要構造LiveData。
LiveData需要DataSource.Factory物件和PagedList.Config物件,只是例項化DataSource.Factory物件需要額外兩個步驟
DataSource.Factory是一個抽象類,例項化時需要實現create()函式,這個函式返回值是一個DataSource類物件
DataSource是一個抽象類,他有三個實現子類:(詳細參考原部落格: https://www.jianshu.com/p/95d44c5338fd )
(1)PageKeyedDataSource 按頁載入,如請求資料時傳入page頁碼。
(2)ItemKeyedDataSource 按條目載入,即請求資料需要傳入其它item的資訊,如載入第n+1項的資料需傳入第n項的id。
(3)PositionalDataSource 按位置載入,如載入指定從第n條到n+20條。
所以我們捋一下思路,首先要定義一個MyDataSource繼承自DataSource的三個子類之一,
再定義一個MyDataSourceFactory繼承自DataSource.Factory,返回值是MyDataSource
然後例項化PagedList.Config,這個類提供有Builder(),比較簡單。
最後將MyDataSourceFactory物件和PagedList.Config物件傳入new LivePagedListBuilder()中得到liveData資料來源。
將liveData資料來源和Adapter繫結是通過觀察者模式實現,呼叫liveData.observe()。
//定義MyDataSource類,繼承自DataSource三個子類之一 private class MyDataSource extends PositionalDataSource<Movie.SubjectsBean>{ @Override public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Movie.SubjectsBean> callback) { callback.onResult(loadData(0, 10), 0, 10);//loadData(0, 10)是我封裝的載入資料方法,網路請求在loadData中,0是初始位置,10是請求10條資料 } @Override public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Movie.SubjectsBean> callback) { callback.onResult(loadData(params.startPosition, count)); } } //定義MyDataSourceFactory,是DataSource.Factory的實現類 private class MyDataSourceFactory extends DataSource.Factory<Integer, Movie.SubjectsBean>{ @Override public DataSource<Integer, Movie.SubjectsBean> create() { return new MyDataSource(); } } //將生產config和liveData的程式碼封裝在這個方法中 private void initPaging(){ PagedList.Config config = new PagedList.Config.Builder() .setPageSize(10)//每頁顯示的詞條數 .setEnablePlaceholders(false) .setInitialLoadSizeHint(10) //首次載入的資料量 .setPrefetchDistance(5)//距離底部還有多少條資料時開始預載入 .build(); /** * LiveData用LivePagedListBuilder生成 * LivePagedListBuilder 構造方法需要 DataSource.Factory和PagedList.Config */ LiveData<PagedList<Movie.SubjectsBean>> liveData = new LivePagedListBuilder(new MyDataSourceFactory(), config) .build(); //觀察者模式,將Adapter註冊進去,當liveData發生改變事通知Adapter liveData.observe(this, new Observer<PagedList<Movie.SubjectsBean>>() { @Override public void onChanged(@Nullable PagedList<Movie.SubjectsBean> subjectsBeans) { adapterHomeInfo.submitList(subjectsBeans); } }); }
從上面的程式碼我們可以看到,資料的入口在MyDataSource的兩個重寫方法裡,裡面的loadData()就是從外部獲取資料的方法,可以按自己想需求來封裝。但是資料載入位於這個位置,就要求我們初始化的時候使用的是本地資料,而來不及進行網路請求。
4.載入網路資料
這麼好的控制元件,我無論如何都想用於純網路載入。想這麼做也很簡單,因為我們已經把與初始化相關的程式碼封裝到initPaging()中了,
我們只需要在合適的時候進行初始化,就能達到延遲載入的目的了。比如,我們可以用handler和網路請求實現訊息非同步處理,在handler的
中呼叫initPaging(),這樣就可以不需要本地資料進行初始化了。
private boolean isDataFresh = false; private boolean isInitPaging = false; private Movie movie; private MyHandler handler = new MyHandler(this); //定義一個MyHandler,用弱引用防止記憶體洩漏 private static class MyHandler<T> extends Handler { WeakReference<ActivityHome> weakReference; public MyHandler(ActivityHome activityHome){ weakReference = new WeakReference<ActivityHome>(activityHome); } @Override public void handleMessage(Message msg) { ActivityHome activity = weakReference.get(); //網路狀況不好時,返回obj為null if(null == msg.obj){ Toast.makeText(activity, "獲取資料失敗,請檢查網路", Toast.LENGTH_SHORT).show(); return; } switch (msg.what){ case Constants._MOVIE_REQUEST: activity.movie = NetWorkUtil.parseJsonWithGson(msg.obj.toString(), Movie.class); activity.isDataFresh = true; if (!activity.isInitPaging) { activity.initPaging(); activity.isInitPaging = true; } break; default: break; } } } /** * 進行網路請求 * * @param startPosition * @param count * @return */ private List<Movie.SubjectsBean> loadData(int startPosition, int count){ List<Movie.SubjectsBean> subjectsBeanList = new ArrayList<>(); if (true == isDataFresh) { subjectsBeanList = movie.getSubjects(); //顯示資料之後,將標誌設定為false,等待下一次網路請求,資料更新完成 isDataFresh = false; //啟動下一次資料重新整理 requestData(handler); } return subjectsBeanList; }
這裡我用了兩個標誌位isDataFresh和isInitPaging,
isInitPaging是確保只在初始化時呼叫initPaging(),
isDataFresh是確保當前網路請求完成後,才會傳送下一次請求,防止同時傳送多個網路請求。