Android分頁組件Paging簡單使用


title: Android分頁組件Paging簡單使用
date: 2018-10-10 17:03:23
tags: Paging


2018-12.21更新

發(fā)現(xiàn)loadRange()方法似乎是在線程中執(zhí)行的死姚,所以這個(gè)方法內(nèi)通過同步網(wǎng)絡(luò)請求獲取數(shù)據(jù)笑诅,重新修改了代碼,提供了一些之前忽略的代碼饰躲。

1.簡介:

Paging組件是Google新推出的分頁組件袱耽,可以輕松幫助開發(fā)者實(shí)現(xiàn)RecyclerView中分頁加載功能。
本文先開坑答捕,等以后用到在詳細(xì)寫明士葫。
推薦博客:http://www.reibang.com/p/1bfec9b9612c

2.Paging庫引入:

在gradle中引入:
implementation 'android.arch.paging:runtime:1.0.0'

剛引入就遇到了錯(cuò)誤,真是一個(gè)不友好的開始:

Error:Execution failed for task ':app:transformDexArchiveWithExternalLibsDexMergerForDebug'.
> java.lang.RuntimeException: java.lang.RuntimeException: com.android.builder.dexing.DexArchiveMergerException: Unable to merge dex

目測哈踱,是導(dǎo)入重復(fù)的包荒适,這個(gè)出錯(cuò)的地方還是google自己的東西。
“大水沖了龍王廟开镣,自家人打自家人”刀诬?
出現(xiàn)沖突的是下面這個(gè)包:

compile 'com.android.support:design:26.1.0'

google了一下,在stackoverflow中找到了解決辦法:
https://stackoverflow.com/questions/49028119/multiple-dex-files-define-landroid-support-design-widget-coordinatorlayoutlayou
即用編譯版本改成27哑子,并且把對應(yīng)的庫版本也改為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,我們先眼熟這幾個(gè)名字卧蜓,下文會(huì)常常出現(xiàn)。
這里采用的是MVVM架構(gòu)把敞。

3.1 Adapter構(gòu)建

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個(gè)不同如下:

  1. Adapter不再繼承自RecyclerView.Adapter弥奸,改為繼承自PagedListAdapter,因?yàn)镻agedListAdapter就是RecyclerView.Adapter的一個(gè)子類奋早。
  2. 定義內(nèi)部回調(diào)接口VideoInfoItemCallback繼承自DiffUtil.ItemCallback<VideoInfo>盛霎,并且實(shí)例化一個(gè)父類引用指向子類(VideoInfoItemCallback)對象
    public static final DiffUtil.ItemCallback<VideoInfo> mDiffCallback = new AdapterPaging.VideoInfoItemCallback();
  3. 重寫構(gòu)造方法,無需參數(shù)傳入耽装,調(diào)用父類構(gòu)造方法將mDiffCallback傳入愤炸。
  4. 通onBindViewHolder中過調(diào)用getItem(position);獲得指定位置的數(shù)據(jù)對象。
    因?yàn)锳dapter中不再需要維護(hù)一個(gè)數(shù)據(jù)List了掉奄,PagedListAdapter中已經(jīng)維護(hù)有规个,并且提供getItem()方法訪問。

3.2 在Activity中的使用

在使用Paging后姓建,我們無需向Adapter中在傳入數(shù)據(jù)源List诞仓,我們需要構(gòu)造LiveData。
LiveData需要DataSource.Factory對象和PagedList.Config對象速兔,只是實(shí)例化DataSource.Factory對象需要額外兩個(gè)步驟
DataSource.Factory是一個(gè)抽象類墅拭,實(shí)例化時(shí)需要實(shí)現(xiàn)create()函數(shù),這個(gè)函數(shù)返回值是一個(gè)DataSource類對象
DataSource是一個(gè)抽象類涣狗,他有三個(gè)實(shí)現(xiàn)子類:(詳細(xì)參考原博客:http://www.reibang.com/p/95d44c5338fd)

(1)PageKeyedDataSource 按頁加載谍婉,如請求數(shù)據(jù)時(shí)傳入page頁碼舒憾。
(2)ItemKeyedDataSource 按條目加載,即請求數(shù)據(jù)需要傳入其它item的信息屡萤,如加載第n+1項(xiàng)的數(shù)據(jù)需傳入第n項(xiàng)的id珍剑。
(3)PositionalDataSource 按位置加載,如加載指定從第n條到n+20條死陆。

所以我們捋一下思路招拙,
1)首先要定義一個(gè)MyDataSource繼承自DataSource的三個(gè)子類之一,
2)再定義一個(gè)MyDataSourceFactory繼承自DataSource.Factory措译,返回值是MyDataSource
3)然后實(shí)例化PagedList.Config,這個(gè)類提供有Builder()别凤,比較簡單。
4)最后將MyDataSourceFactory對象和PagedList.Config對象傳入new LivePagedListBuilder()中得到liveData數(shù)據(jù)源领虹。
將liveData數(shù)據(jù)源和Adapter綁定是通過觀察者模式實(shí)現(xiàn)规哪,調(diào)用liveData.observe()。

//定義MyDataSource類塌衰,繼承自DataSource三個(gè)子類之一
    private class MyDataSource extends PositionalDataSource<Movie.SubjectsBean>{

        @Override
        public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Movie.SubjectsBean> callback) {
            callback.onResult(initData(), 0, 10);//initData()是我封裝的加載數(shù)據(jù)方法
        }

        @Override
        public void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Movie.SubjectsBean> callback) {
            callback.onResult(syncRequestData().getSubjects());
        }
    }
    //定義MyDataSourceFactory诉稍,是DataSource.Factory的實(shí)現(xiàn)類
    private class MyDataSourceFactory extends DataSource.Factory<Integer, Movie.SubjectsBean>{

        @Override
        public DataSource<Integer, Movie.SubjectsBean> create() {
            return new MyDataSource();
        }
    }
    //將生產(chǎn)config和liveData的代碼封裝在這個(gè)方法中
    private void initPaging(){
        PagedList.Config config = new PagedList.Config.Builder()
                .setPageSize(10)    //每頁顯示的詞條數(shù)
                .setEnablePlaceholders(false)
                .setInitialLoadSizeHint(10) //首次加載的數(shù)據(jù)量
                .setPrefetchDistance(5)     //距離底部還有多少條數(shù)據(jù)時(shí)開始預(yù)加載
                .build();

        /**
         * LiveData用LivePagedListBuilder生成
         * LivePagedListBuilder 構(gòu)造方法需要 DataSource.Factory和PagedList.Config
         */
        LiveData<PagedList<Movie.SubjectsBean>> liveData = new LivePagedListBuilder(new MyDataSourceFactory(), config)
                .build();
        //觀察者模式,將Adapter注冊進(jìn)去最疆,當(dāng)liveData發(fā)生改變事通知Adapter
        liveData.observe(this, new Observer<PagedList<Movie.SubjectsBean>>() {
            @Override
            public void onChanged(@Nullable PagedList<Movie.SubjectsBean> subjectsBeans) {
                adapterHomeInfo.submitList(subjectsBeans);
            }
        });
    }

從上面的代碼我們可以看到杯巨,數(shù)據(jù)的入口在MyDataSource的兩個(gè)重寫方法里:
initData():就是用于數(shù)據(jù)加載的方法,可以按自己想需求來封裝努酸。
syncRequestData():用于"加載更多"的方法服爷,loadRange()似乎本身就處在子線程中,所以此處可以用同步網(wǎng)絡(luò)請求获诈。
initData()在初始化時(shí)調(diào)用仍源,所以要求我們初始化的時(shí)候使用的是本地?cái)?shù)據(jù),而來不及進(jìn)行網(wǎng)絡(luò)請求舔涎。

4.加載網(wǎng)絡(luò)數(shù)據(jù)

這么好的控件笼踩,我無論如何都想用于純網(wǎng)絡(luò)加載。想這么做也很簡單亡嫌,因?yàn)槲覀円呀?jīng)把與初始化相關(guān)的代碼封裝到initPaging()中了戳表。
我們只需要在合適的時(shí)候進(jìn)行初始化,就能達(dá)到延遲加載的目的了昼伴。比如匾旭,我們可以用handler和網(wǎng)絡(luò)請求實(shí)現(xiàn)消息異步處理,在handler的中調(diào)用initPaging(),這樣就可以不需要本地?cái)?shù)據(jù)進(jìn)行初始化了圃郊。


public static final String MOVIE_REQUEST= 1;
private Movie movie;
private MyHandler handler = new MyHandler(this);
//定義一個(gè)MyHandler价涝,用弱引用防止內(nèi)存泄漏
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();
        //網(wǎng)絡(luò)狀況不好時(shí),返回obj為null
        if(null == msg.obj){
            Toast.makeText(activity, "獲取數(shù)據(jù)失敗持舆,請檢查網(wǎng)絡(luò)", Toast.LENGTH_SHORT).show();
            return;
        }
        switch (msg.what){
            case MOVIE_REQUEST:
                activity.movie = NetWorkUtil.parseJsonWithGson(msg.obj.toString(), Movie.class);
                activity.initPaging();
                break;

            default:
                break;
        }
    }
}
/**
 * 初始化數(shù)據(jù)
 * @return
 */
private List<Movie.SubjectsBean> initData(){
    List<Movie.SubjectsBean> subjectsBeanList = new ArrayList<>();
    subjectsBeanList = movie.getSubjects();
    return subjectsBeanList;
}
/**
 * 進(jìn)行網(wǎng)絡(luò)請求色瘩,同步
 * @return
 */
private Movie syncRequestData(){
        //將uri中pageindex對應(yīng)的參數(shù)+1伪窖,然后進(jìn)行同步網(wǎng)絡(luò)請求
        int currentPageNumber = Integer.parseInt(uri.getQueryParameter("pageindex"));
        String url = replace(uri.toString(), "pageindex", currentPageNumber+1+"");
        return NetWorkUtil.syncRequest(url, Movie.class);
}
/**
 * 進(jìn)行網(wǎng)絡(luò)請求,異步
 * @return
 */
private void asynRequestData(MyHandler handler){
        //將uri中pageindex對應(yīng)的參數(shù)+1居兆,然后進(jìn)行異步網(wǎng)絡(luò)請求
        int currentPageNumber = Integer.parseInt(uri.getQueryParameter("pageindex"));
        String url = replace(uri.toString(), "pageindex", currentPageNumber+1+"");
        NetWorkUtil.sendRequestWithOkHttp(url, MOVIE_REQUEST, handler);
}

//將Uri中的參數(shù)重新賦值
public static String replace(String url, String key, String value) {
    if (!TextUtils.isEmpty(url) && !TextUtils.isEmpty(key)) {
        url = url.replaceAll("(" + key + "=[^&]*)", key + "=" + value);
    }
    return url;
}

代碼完成了覆山,思路很簡單。
我們只需要在onCreate()中調(diào)用asynRequestData()方法泥栖,開始請求數(shù)據(jù)簇宽,數(shù)據(jù)得到后開始初始化Paging組件,之后通過同步網(wǎng)絡(luò)請求獲得后續(xù)的數(shù)據(jù)吧享。
關(guān)于同步網(wǎng)絡(luò)請求syncRequest()和異步網(wǎng)絡(luò)請求sendRequestWithOkHttp()我就不在這里給出了魏割,大家可以用不同的庫去實(shí)現(xiàn)。

5.結(jié)束

又到招聘的季節(jié)了钢颂,給大家發(fā)個(gè)字節(jié)跳動(dòng)的內(nèi)推福利:
內(nèi)推碼:UDXTM7B
投遞鏈接:https://job.toutiao.com/campus/
歡迎大家投遞钞它。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市殊鞭,隨后出現(xiàn)的幾起案子遭垛,更是在濱河造成了極大的恐慌,老刑警劉巖操灿,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锯仪,死亡現(xiàn)場離奇詭異,居然都是意外死亡牲尺,警方通過查閱死者的電腦和手機(jī)卵酪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門幌蚊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谤碳,“玉大人,你說我怎么就攤上這事溢豆⊙鸭颍” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵漩仙,是天一觀的道長搓茬。 經(jīng)常有香客問我,道長队他,這世上最難降的妖魔是什么卷仑? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮麸折,結(jié)果婚禮上锡凝,老公的妹妹穿的比我還像新娘。我一直安慰自己垢啼,他們只是感情好窜锯,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布张肾。 她就那樣靜靜地躺著,像睡著了一般锚扎。 火紅的嫁衣襯著肌膚如雪吞瞪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天驾孔,我揣著相機(jī)與錄音芍秆,去河邊找鬼。 笑死助币,一個(gè)胖子當(dāng)著我的面吹牛浪听,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播眉菱,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼迹栓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俭缓?” 一聲冷哼從身側(cè)響起克伊,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎华坦,沒想到半個(gè)月后愿吹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡犁跪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坷衍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枫耳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出孟抗,到底是詐尸還是另有隱情迁杨,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布铅协,位于F島的核電站摊沉,受9級特大地震影響狐史,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一预皇、第九天 我趴在偏房一處隱蔽的房頂上張望侈玄。 院中可真熱鬧,春花似錦序仙、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽治唤。三九已至糙申,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間柜裸,已是汗流浹背缕陕。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工扛邑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔬崩。 一個(gè)月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓搀暑,卻偏偏與公主長得像沥阳,于是被迫代替她去往敵國和親险掀。 傳聞我的和親對象是個(gè)殘疾皇子湾宙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評論 2 345

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,515評論 25 707
  • 用兩張圖告訴你,為什么你的 App 會(huì)卡頓? - Android - 掘金 Cover 有什么料埠啃? 從這篇文章中你...
    hw1212閱讀 12,693評論 2 59
  • afinalAfinal是一個(gè)android的ioc伟恶,orm框架 https://github.com/yangf...
    passiontim閱讀 15,401評論 2 45
  • 今晚還是那樣的想你,耳邊的歌詞讓我更加的想你: It's a quarter after one I'm all ...
    jryc閱讀 324評論 0 0
  • 1.Hive中內(nèi)部表和外部表及其對應(yīng)的hdfs路徑 1.1 Hive內(nèi)部表和外部表的區(qū)別 Hive 創(chuàng)建內(nèi)部表時(shí)巴碗,...
    StoneHeart閱讀 982評論 0 1