Jetpack-Android Paging Library

目錄:

需求來(lái)源
Paging運(yùn)作流程
Paging三大部件
如何使用
三種DataSource對(duì)比
LivePagedListBuilder本慕、RxPagedListBuilder對(duì)比

一、需求來(lái)源:

為方便實(shí)現(xiàn)上拉加載恒水、簡(jiǎn)化代碼效拭、上拉加載邏輯可配置...總之就是為了方便

Paging出現(xiàn)前暂吉,上拉加載觸發(fā)一般是通過(guò):

  1. 監(jiān)聽(tīng)RecyclerView的滾動(dòng)事件,判斷RecyclerView是否滾動(dòng)到底部
  2. 處理Adapter的onBindViewHolder方法缎患,根據(jù)位置與數(shù)量判斷當(dāng)前位置item是否該觸發(fā)上拉加載
  3. 或者其他方式...

如方式一:

        recycler_view.addOnScrollListener(object : RecyclerView.OnScrollListener() {

            var shouldReload = false

            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE && shouldReload) {
                    // 加載下一頁(yè)
                    ...
                }
            }

            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                val layoutManager: LinearLayoutManager =
                    recycler_view.layoutManager as LinearLayoutManager
                layoutManager.apply {

                    val firstVisibleItem = findFirstVisibleItemPosition()
                    val visibleItemCount = childCount
                    val totalItemCount = itemCount
                    shouldReload = firstVisibleItem + visibleItemCount == totalItemCount && dy > 0
                }
            }
        })

BaseRecyclerViewAdapterHelper庫(kù)實(shí)現(xiàn)方式為方案二慕的,部分代碼如下:

    @Override
    public void onBindViewHolder(@NonNull K holder, int position) {
        ...  // 其他代碼
        autoLoadMore(position);
        ...  // 其他代碼
    }

    @Override
    public void onBindViewHolder(@NonNull K holder, int position, @NonNull List<Object> payloads) {
        ...  // 其他代碼
        autoLoadMore(position);
        ...  // 其他代碼
    }

    // 自動(dòng)上拉加載判斷邏輯
    private void autoLoadMore(int position) {

        // mPreLoadNumber為預(yù)加載控制數(shù)量,默認(rèn)為1
        if (position < getItemCount() - mPreLoadNumber) {
            return;
        }
        if (mLoadMoreView.getLoadMoreStatus() != LoadMoreView.STATUS_DEFAULT) {
            return;
        }
        
        ... // 其他代碼
        mRequestLoadMoreListener.onLoadMoreRequested();
        ... // 其他代碼
    }

Paging運(yùn)作流程

首先挤渔,Adapter會(huì)繼承自PagedListAdapter(DiffUtils)肮街,PagedListAdapter(DiffUtils)最終也是繼承自RecyclerView.Adapter。執(zhí)行到onBindViewHolder()數(shù)據(jù)綁定時(shí)判导,調(diào)用getItem(position)獲取當(dāng)前位置的數(shù)據(jù)嫉父,此時(shí)PagedListAdapter就會(huì)知道列表現(xiàn)在處于什么位置沛硅,以及是否觸發(fā)加載下一頁(yè)功能。當(dāng)加載下一頁(yè)功能被觸發(fā)時(shí)绕辖,會(huì)通知內(nèi)部PagedListDataSource拉取數(shù)據(jù)摇肌,獲得數(shù)據(jù)后通過(guò)DiffUtils對(duì)比得到最終數(shù)據(jù)集

Paging.png

Paging三大部件

1. PagedListAdapter

繼承自RecyclerView.Adapter,用來(lái)承載PagedList仪际。PagedListAdapter構(gòu)造函數(shù)需提供DiffUtil.ItemCallback對(duì)象围小,或者是AsyncDifferConfig對(duì)象。

    protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
        mDiffer.addPagedListListener(mListener);
    }

    protected PagedListAdapter(@NonNull AsyncDifferConfig<T> config) {
        mDiffer = new AsyncPagedListDiffer<>(new AdapterListUpdateCallback(this), config);
        mDiffer.addPagedListListener(mListener);
    }

數(shù)據(jù)對(duì)比主要是用實(shí)例化的AsyncDifferConfig對(duì)象树碱,其構(gòu)造方法如下:

    public AsyncPagedListDiffer(@NonNull RecyclerView.Adapter adapter,
            @NonNull DiffUtil.ItemCallback<T> diffCallback) {
        mUpdateCallback = new AdapterListUpdateCallback(adapter);
        mConfig = new AsyncDifferConfig.Builder<>(diffCallback).build();
    }

    @SuppressWarnings("WeakerAccess")
    public AsyncPagedListDiffer(@NonNull ListUpdateCallback listUpdateCallback,
            @NonNull AsyncDifferConfig<T> config) {
        mUpdateCallback = listUpdateCallback;
        mConfig = config;
    }

2. PagedList

PagedList是一個(gè)抽象類肯适,是一個(gè)List的封裝類別,與我們熟悉的ArrayList一樣繼承自AbstractList成榜。用來(lái)存儲(chǔ)分頁(yè)載入的數(shù)據(jù)集合框舔,并且通知DataSource數(shù)據(jù)加載時(shí)機(jī)∈昊椋可以通過(guò)PagedList.Config配置

(1). mPageSize:頁(yè)面大小即每次加載時(shí)加載的數(shù)量
(2). mPrefetDistance:預(yù)取距離刘绣,給定UI中最后一個(gè)可見(jiàn)的Item,超過(guò)這個(gè)Item應(yīng)該預(yù)取一段數(shù)據(jù)

PagingConfig.png

3. DataSource

負(fù)責(zé)實(shí)現(xiàn)數(shù)據(jù)載入實(shí)現(xiàn)挣输,常用子類如下:

  1. PageKeyedDataSource:當(dāng)后一頁(yè)的取得方式從當(dāng)前頁(yè)得知额港,通過(guò)當(dāng)前頁(yè)相關(guān)的key來(lái)獲取數(shù)據(jù)。

假定一個(gè)場(chǎng)景幫助理解:瀏覽短視頻數(shù)據(jù)時(shí)返回的每個(gè)視頻攜帶視頻類別歧焦,當(dāng)哪個(gè)視頻觀看比較久移斩,則下一頁(yè)優(yōu)先獲取該類別的視頻--后一頁(yè)的請(qǐng)求,根據(jù)前一頁(yè)的key(視頻類別)獲取
簡(jiǎn)單常見(jiàn)的按照頁(yè)序號(hào)查詢數(shù)據(jù)也可以用這個(gè)DataSource绢馍。初始時(shí)callback設(shè)置前后頁(yè)key為-1,1向瓷。加載下一頁(yè)設(shè)置callback的key遞增1,加載上一頁(yè)設(shè)置callback的key遞減1.

  1. PositionalDataSource:通過(guò)在數(shù)據(jù)中的position作為key來(lái)獲取下一頁(yè)數(shù)據(jù)舰涌。數(shù)據(jù)排一排猖任,根據(jù)配置的起始位置和獲取數(shù)量,獲取從指定位置開(kāi)始后續(xù)n條數(shù)據(jù)瓷耙。比如說(shuō)朱躺,請(qǐng)求返回從第100條數(shù)據(jù)開(kāi)始的之后20條數(shù)據(jù)。

假如設(shè)置初始請(qǐng)求從第30項(xiàng)開(kāi)始搁痛,獲取15條數(shù)據(jù)长搀。此時(shí)下一頁(yè)請(qǐng)求參數(shù)為從第45項(xiàng)開(kāi)始,獲取15條數(shù)據(jù)鸡典。參數(shù)中startPosition=requestedStartPosition+pageSize

  1. ItemKeyedDataSource:通過(guò)當(dāng)前頁(yè)數(shù)據(jù)信息(具體的item)作為key源请,來(lái)獲取下一頁(yè)數(shù)據(jù)。必須由當(dāng)前頁(yè)數(shù)據(jù)去加載下一頁(yè)數(shù)據(jù)。

場(chǎng)景:例如小紅書谁尸、資訊類的app舅踪,上下頁(yè)的請(qǐng)求參數(shù)都是從當(dāng)前頁(yè)配置的

如何使用

1. 定義DiffCallback

boolean areItemsTheSame(@NonNull DataBean oldItem, @NonNull DataBean newItem):判斷是不是同一個(gè)Item
boolean areContentsTheSame(@NonNull DataBean oldItem, @NonNull DataBean newItem):判斷兩個(gè)Item內(nèi)容是否相同,當(dāng)areItemsTheSame為true時(shí)才調(diào)用

2. 初始化適配器良蛮,綁定RecyclerView

使用基本與RecyclerView.Adapter一樣

差異點(diǎn):需傳入DiffUtil.ItemCallback抽碌。獲取數(shù)據(jù)使用getItem(position)方法

    private class MyAdapter extends PagedListAdapter<DataBean,  ViewHolder> {
        public MyAdapter() {
            super(mDiffCallback);
        }

        ... // 其他代碼

        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            DataBean data = getItem(position);
            ... // 填充Item
        }
    }
3. 設(shè)置PagedList.Config
        PagedList.Config mPagedListConfig = new PagedList.Config.Builder()
                .setPageSize(2)                 // 設(shè)置后續(xù)頁(yè)面(非第一頁(yè))每頁(yè)加載的數(shù)量
                .setPrefetchDistance(2)         // getItem(position)時(shí)調(diào)用
                .setEnablePlaceholders(true)    // 設(shè)置占位符,默認(rèn)true
                .setInitialLoadSizeHint(3)      // 設(shè)置第一頁(yè)加載的數(shù)量
                .build();
4. 設(shè)置DataSource
    private class MyDataSource extends PageKeyedDataSource<Integer, DataBean> {

        @Override
        public void loadInitial(@NonNull final LoadInitialParams<Integer> params,
                                @NonNull final LoadInitialCallback<Integer, DataBean> callback) {
             // 請(qǐng)求第一頁(yè)數(shù)據(jù)
        }

        @Override
        public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, DataBean> callback) {

        }

        @Override
        public void loadAfter(@NonNull final LoadParams<Integer> params,
                              @NonNull final LoadCallback<Integer, DataBean> callback) {
             // 請(qǐng)求下一頁(yè)數(shù)據(jù)
        }
    }
5. 設(shè)置DataSourceFactory
    private class DataSourceFactory extends DataSource.Factory<Integer, DataBean> {

        @NonNull
        @Override
        public DataSource<Integer, DataBean> create() {
            return new MyDataSource();
        }
    }
6. 設(shè)置LiveData
        LivePagedListBuilder<Integer, DataBean> builder = new LivePagedListBuilder<>(
                new DataSourceFactory(), mPagedListConfig);
        LiveData<PagedList<DataBean>> mPagedList = builder.build();
        mPagedList.observe(this, new Observer<PagedList<DataBean>>() {

            @Override
            public void onChanged(PagedList<DataBean> o) {
                // 填充數(shù)據(jù)  
                mAdapter.submitList(o);
            }
        });

三種DataSource對(duì)比

1. PositionalDataSource:根據(jù)列表的絕對(duì)位置獲取數(shù)據(jù)
/**
 * desc:根據(jù)列表的絕對(duì)位置決定放數(shù)據(jù)</br>
 * 從任意指定位置開(kāi)始獲取數(shù)據(jù)
 * time: 2019/11/12-11:25</br>
 * author:Leo </br>
 */
注:其中的泛型Item是請(qǐng)求到列表每項(xiàng)數(shù)據(jù)的實(shí)體類
class TestPositionalDataSource : PositionalDataSource<Item>() {

    override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<Item>) {
        params.apply {
            val items = getIncreaseItems(startPosition, loadSize)
            callback.onResult(items)
        }
    }

    override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback<Item>) {
        params.apply {
            val items = getIncreaseItems(requestedStartPosition, pageSize)
            callback.onResult(items, 0)
        }
    }
}

該方式的關(guān)鍵點(diǎn)在于LoadInitialParamsLoadRangeParams中參數(shù)的獲取

ContiguousPagedList.java
175行 ContiguousPagedList構(gòu)造函數(shù)
mDataSource.dispatchLoadInitial(key,
     mConfig.initialLoadSizeHint,
     mConfig.pageSize,
     mConfig.enablePlaceholders,
     mMainThreadExecutor,
     mReceiver);
  • LoadInitialParams各參數(shù)與PagedList.Config對(duì)應(yīng)關(guān)系如下:
    // key必須為Integer類型
    requestedStartPosition -> LivePagedListBuilder.setInitialLoadKey
    requestedLoadSize -> config.initialLoadSizeHint
    pageSize -> config.pageSize
    placeholdersEnabled -> false // 該模式下寫死為false
    PositionalDataSource.java

    final void dispatchLoadInitial(...) {
        ...
        LoadInitialParams params = new LoadInitialParams(
                requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
        loadInitial(params, callback);
        ...
    }

初次加載數(shù)據(jù)時(shí)决瞳,參數(shù)值讀取的參數(shù)如上構(gòu)造函數(shù)`LoadInitialParams`
注意:此時(shí)placeholdersEnabled實(shí)際為false

LoadRangeParams參數(shù)值來(lái)源

  • 取下一頁(yè) dispatchLoadAfter
    startPosition -> 當(dāng)前位置+1
    loadSize -> config.pageSize
  • 取上一頁(yè) dispatchLoadBefore
    參數(shù)值根據(jù)不同情景賦值咬展,源碼如下
PositionalDataSource.java
@Override
void dispatchLoadBefore(...) {
    int startIndex = currentBeginIndex - 1;
    if (startIndex < 0) {
         // 列表為空 情景一
         mSource.dispatchLoadRange(
               PageResult.PREPEND, startIndex, 0, mainThreadExecutor,receiver);
    } else {
         // 列表不為空 情景二
         int loadSize = Math.min(pageSize, startIndex + 1);
         startIndex = startIndex - loadSize + 1;
         mSource.dispatchLoadRange(
               PageResult.PREPEND, startIndex, loadSize, mainThreadExecutor, receiver);
    }
}

LoadRangeParams參數(shù)值來(lái)源
情景一:此時(shí)列表為空,此時(shí)
    startPosition -> -1 
    loadSize -> 0
情景二:正常加載之前數(shù)據(jù)
    startPosition -> 當(dāng)前位置向前推loadSize個(gè)數(shù)
    loadSize -> 當(dāng)前位置與config.pageSize的最小者(避免可能出現(xiàn)當(dāng)前位置之前的數(shù)量少于pageSize情況)

總結(jié):初始化加載時(shí)瞒斩,參數(shù)從PagedList配置項(xiàng)讀取,無(wú)占位符涮总。加載前后數(shù)據(jù)時(shí)胸囱,開(kāi)始位置根據(jù)當(dāng)前位置向前/后推算,加載數(shù)量讀取自config.pageSize瀑梗。該類型DataSource無(wú)法加載上一頁(yè)

  • 此時(shí)獲取上頁(yè)/下頁(yè)數(shù)據(jù)僅僅根據(jù)位置序號(hào)就能獲得烹笔。

使用注意點(diǎn):
使用PositionalDataSource時(shí),需注意需設(shè)置config.enablePlaceholders=false抛丽,否則會(huì)崩潰谤职。原因詳解如下:

    PagedList.class
    @NonNull
    static <K, T> PagedList<T> create(...) {
        /**
         * PositionalDataSource時(shí),dataSource.isContiguous()恒等于false
         * 此時(shí)config.enablePlaceholders=false才執(zhí)行if邏輯
         **/
        if (dataSource.isContiguous() || !config.enablePlaceholders) {
            ...其他代碼
            return new ContiguousPagedList<>(contigDataSource,
                    notifyExecutor, fetchExecutor,
                    boundaryCallback, config, key, lastLoad);
        } else {
            return new TiledPagedList<>((PositionalDataSource<T>) dataSource,
                    notifyExecutor, fetchExecutor, boundaryCallback,
                    config, (key != null) ? (Integer) key : 0);
        }
    }

create方法生成的ContiguousPagedList亿鲜、TiledPagedList對(duì)象最終都會(huì)調(diào)用PositionalDataSource.dispatchLoadInitial方法允蜈,如下:
    final void dispatchLoadInitial(...) {
        /**
         * 主要生成LoadInitialCallbackImpl對(duì)象
         * ContiguousPagedList方式調(diào)用時(shí)傳的acceptCount為false,TiledPagedList傳的為true
         **/
        LoadInitialCallbackImpl<T> callback =
                new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
        ... 其他代碼
    }

繼續(xù)看LoadInitialCallbackImpl代碼:
    static class LoadInitialCallbackImpl<T> extends LoadInitialCallback<T> {
        ... // 其他代碼
        LoadInitialCallbackImpl(@NonNull PositionalDataSource dataSource, boolean countingEnabled, int pageSize, PageResult.Receiver<T> receiver) {
             ... // 其他代碼

            // ContiguousPagedList方式調(diào)用時(shí)傳的acceptCount為false蒿柳,TiledPagedList傳的為true
            mCountingEnabled = countingEnabled;
             ... // 其他代碼
        }

        @Override
        public void onResult(@NonNull List<T> data, int position, int totalCount) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                ... // 其他代碼

                if (mCountingEnabled) {
                    int trailingUnloadedCount = totalCount - position - data.size();
                    mCallbackHelper.dispatchResultToReceiver(
                            new PageResult<>(data, position, trailingUnloadedCount, 0));
                }
                ... // 其他代碼
            }
        }

        @Override
        public void onResult(@NonNull List<T> data, int position) {
            if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
                ... // 其他代碼
                if (mCountingEnabled) {
                    throw new IllegalStateException("Placeholders requested, but totalCount not"
                            + " provided. Please call the three-parameter onResult method, or"
                            + " disable placeholders in the PagedList.Config");
                }
                ... // 其他代碼
            }
        }
    }
從onResult方法代碼可知mCountingEnabled為true一定會(huì)拋出異常饶套。
所以在PagedList.create方法中需保證不創(chuàng)建TiledPagedList對(duì)象,即設(shè)置config.enablePlaceholder=false
2. PageKeyedDataSource:原始數(shù)據(jù)已有分頁(yè)功能垒探,根據(jù)每頁(yè)的Key取得上下頁(yè)數(shù)據(jù)
/**
 * desc:原始數(shù)據(jù)已有分頁(yè)功能妓蛮,根據(jù)每頁(yè)的Key取得數(shù)據(jù)</br>
 * time: 2019/11/12-11:25</br>
 * author:Leo </br>
 */
注:其中的泛型Int是用于請(qǐng)求上下頁(yè)數(shù)據(jù)的參數(shù)key實(shí)體類型
    其中的泛型Item是請(qǐng)求到列表每項(xiàng)數(shù)據(jù)的實(shí)體類
class TestPageKeyedDataSource : PageKeyedDataSource<Int, Item>() {
    override fun loadInitial(
        params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Item>
    ) {
        val items = getItems(params.requestedLoadSize)
        callback.onResult(items, items[0].id, items.last().id)
    }

    // 這里的參數(shù)key實(shí)際就是上述的items.last().id
    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Item>) {
        params.apply {
            val items = getIncreaseItems(key, requestedLoadSize)
            callback.onResult(items, items.last().id)
        }
    }

    // 這里的參數(shù)key實(shí)際就是上述的items[0].id
    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Item>) {
        params.apply {
            val items = getReduceItems(key, requestedLoadSize)
            callback.onResult(items, items.first().id)
        }
    }
}

LoadInitialParams參數(shù)與PositionalDataSource一致。但構(gòu)造函數(shù)只取了requestedLoadSizeplaceholdersEnabled這兩個(gè)參數(shù)圾叼。調(diào)用代碼如下:

    PageKeyedDataSource.java
    @Override
    final void dispatchLoadInitial(...) {
        LoadInitialCallbackImpl<Key, Value> callback =
                new LoadInitialCallbackImpl<>(this, enablePlaceholders, receiver);
        loadInitial(new LoadInitialParams<Key>(initialLoadSize, enablePlaceholders), callback);
        ...
    }

注意:該類型DataSource蛤克,參數(shù)值只取了`requestedLoadSize`和`placeholdersEnabled`這兩個(gè)參數(shù)

LoadParams包含兩個(gè)參數(shù)key(上頁(yè)/下頁(yè)的key) requestedLoadSize(需要加載的數(shù)量)
requestedLoadSize:即為config.pageSize
key:的賦值/初始化時(shí)機(jī)都在上述TestPageKeyedDataSource類的幾個(gè)callback.onResult(...)方法中

參數(shù)key的賦值:

    @Override
    final void dispatchLoadAfter(...) {
        @Nullable Key key = getNextKey();
        ...
        loadAfter(new LoadParams<>(key, pageSize),...);
        ...
    }

    @Override
    final void dispatchLoadBefore(...) {
        @Nullable Key key = getPreviousKey();
        ...
        loadBefore(new LoadParams<>(key, pageSize),...);
        ...
    }
上下頁(yè)key的賦值,關(guān)鍵代碼如下:

1. void initKeys(@Nullable Key previousKey, @Nullable Key nextKey)
2. void setPreviousKey(@Nullable Key previousKey)
3. void setNextKey(@Nullable Key nextKey)

initKeys方法在LoadInitialCallback.onResult(...)中夷蚊。精簡(jiǎn)代碼如下
static class LoadInitialCallbackImpl<Key, Value> extends LoadInitialCallback<Key, Value> {  
    ...
    LoadInitialCallbackImpl(...) {
        ...
    }

   @Override
   public void onResult(@NonNull List<Value> data, int position, int totalCount,
                @Nullable Key previousPageKey, @Nullable Key nextPageKey) {
        ...
        // setup keys before dispatching data, so guaranteed to be ready
        mDataSource.initKeys(previousPageKey, nextPageKey);
        ...
   }

      @Override
      public void onResult(@NonNull List<Value> data, @Nullable Key previousPageKey,
                @Nullable Key nextPageKey) {
        ...
        mDataSource.initKeys(previousPageKey, nextPageKey);
        ...
   }
}

static class LoadCallbackImpl<Key, Value> extends LoadCallback<Key, Value> {
    ...
    @Override
    public void onResult(@NonNull List<Value> data, @Nullable Key adjacentPageKey) {
        ...
        if (mCallbackHelper.mResultType == PageResult.APPEND) {
            mDataSource.setNextKey(adjacentPageKey);
        } else {
            mDataSource.setPreviousKey(adjacentPageKey);
        }
        ...
    }
}

總結(jié):初始化加載時(shí)构挤,會(huì)設(shè)置上頁(yè)/下頁(yè)的關(guān)鍵值作為獲取上頁(yè)/下頁(yè)數(shù)據(jù)的參數(shù)。每頁(yè)需加載的數(shù)量從config.pageSize獲取

  • 每頁(yè)數(shù)據(jù)都返回兩個(gè)參數(shù):獲取上頁(yè)數(shù)據(jù)的key惕鼓,獲取下頁(yè)數(shù)據(jù)的key儿倒。且獲取每頁(yè)數(shù)據(jù)時(shí)必須傳入key才能請(qǐng)求到該頁(yè)數(shù)據(jù)
  • 只要正確配置previousPageKeynextPageKey(上述代碼中的items[0].id、items.last().id),上下滑動(dòng)都可以加載數(shù)據(jù)夫否,在第一頁(yè)時(shí)也能滑動(dòng)
3. ItemKeyedDataSource:當(dāng)列表數(shù)據(jù)的Key有連續(xù)性彻犁,可根據(jù)Key找到下一頁(yè)或上一頁(yè)數(shù)據(jù)
/**
 * desc:當(dāng)列表數(shù)據(jù)的Key有連續(xù)性,可根據(jù)Key找到下一頁(yè)或上一頁(yè)數(shù)據(jù)</br>
 * time: 2019/11/12-11:24</br>
 * author:Leo </br>
 */
注:泛型Int即為getKey()方法返回的類型
    泛型Item為列表各項(xiàng)的實(shí)體類型
class TestItemKeyedDataSource : ItemKeyedDataSource<Int, Item>() {

    // 因?yàn)槊宽?yè)請(qǐng)求都需要key凰慈,所以需配置一個(gè)初始key-requestedInitialKey 
    override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Item>) {
        params.apply {
            callback.onResult(getIncreaseItems(requestedInitialKey ?: 0, requestedLoadSize))
        }
    }

    // 這里的key是適配器中數(shù)據(jù)集最后一項(xiàng)數(shù)據(jù)的item.id
    override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Item>) {
        params.apply {
            callback.onResult(getIncreaseItems(key + 1, requestedLoadSize))
        }
    }

    // 這里的key是適配器中數(shù)據(jù)集第一項(xiàng)數(shù)據(jù)的item.id
    override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Item>) {
        params.apply {
            callback.onResult(getIncreaseItems(key, requestedLoadSize))
        }
    }

    override fun getKey(item: Item) = item.id
}

LoadInitialParams參數(shù)與上述兩類型參數(shù)相同汞幢,但構(gòu)造方法有點(diǎn)區(qū)別,精簡(jiǎn)源碼如下:

    ItemKeyedDataSource.java

    @Override
    final void dispatchLoadInitial(...) {
        ...
        loadInitial(new LoadInitialParams<>(key, initialLoadSize, enablePlaceholders), callback);
        ...
    }

LoadInitialParams各參數(shù)與PagedList.Config對(duì)應(yīng)關(guān)系如下:
requestedStartPosition -> LivePagedListBuilder.setInitialLoadKey()
requestedLoadSize -> config.initialLoadSizeHint
placeholdersEnabled -> config.enablePlaceholders
注:初次加載數(shù)據(jù)不需指定加載數(shù)據(jù)pageSize

LoadParams包含兩個(gè)參數(shù)key(上頁(yè)/下頁(yè)的key) requestedLoadSize(需要加載的數(shù)量)
requestedLoadSize:即為config.pageSize
key:該key一般都為請(qǐng)求返回實(shí)體類中的一個(gè)字段微谓,父類根據(jù)上頁(yè)/下頁(yè)拿到緩存下集合中的第一個(gè)或最后一個(gè)數(shù)據(jù)實(shí)體森篷。然后根據(jù)子類中重寫的getKey(item: T)方法指定key所對(duì)應(yīng)的字段。部分代碼如下:

    ItemKeyedDataSource.java

    @Override
    final void dispatchLoadAfter(...) {
        loadAfter(new LoadParams<>(getKey(currentEndItem), pageSize),...);
    }

    @Override
    final void dispatchLoadBefore(...) {
        loadBefore(new LoadParams<>(getKey(currentBeginItem), pageSize),...);
    }

getKey(currentEndItem)豺型、getKey(currentBeginItem)仲智。getKey為抽象方法,
子類重寫指定對(duì)應(yīng)字段姻氨,如下:

TestItemKeyedDataSource.java
override fun getKey(item: Item) = item.id

總結(jié):初始化加載時(shí)钓辆,根據(jù)config配置加載當(dāng)前頁(yè)數(shù)據(jù)。渲染數(shù)據(jù)的同時(shí)也會(huì)緩存到Storage中肴焊。當(dāng)獲取上頁(yè)/下頁(yè)數(shù)據(jù)時(shí)前联,取得Storage中的第一條/最后一條數(shù)據(jù)。再通過(guò)子類重寫的getKey方法娶眷,拿到請(qǐng)求所需實(shí)際的key似嗤。

LivePagedListBuilder、RxPagedListBuilder對(duì)比

從兩個(gè)點(diǎn)對(duì)比:使用方式届宠,值回調(diào)方式

  1. 如何使用
    /**
     * 最終返回的是 LiveData<PagedList<T>>類型對(duì)象
     */
    fun allTestLiveData(id: String) = this.let {
        LivePagedListBuilder(
            createFactory(id), PagedList.Config.Builder()
                .setPageSize(10)
                .setInitialLoadSizeHint(13)
                .setEnablePlaceholders(false)
                .setPrefetchDistance(3).build()
        ).build()
    }.observe(this, Observer { adapter.submitList(it) })

    /**
     * 最終返回的是 Flowable<PagedList<T>>類型對(duì)象
     */
    fun allTestFlowable(id: String) = this.let {
        RxPagedListBuilder(
            createFactory(id), PagedList.Config.Builder()
                .setPageSize(10)
                .setInitialLoadSizeHint(13)
                .setEnablePlaceholders(false)
                .setPrefetchDistance(3).build()
        ).buildFlowable(BackpressureStrategy.DROP)
    }.subscribe { adapter.submitList(it) }
  1. 值回調(diào)方式
    兩種Builder一個(gè)為L(zhǎng)iveData烁落,一個(gè)為RxJava。
    先看第一個(gè)LivePagedListBuilder豌注。
    LivePagedListBuilder通過(guò)build方法構(gòu)造了一個(gè)ComputableLiveData對(duì)象顽馋,最終會(huì)執(zhí)行一個(gè)Runnable,看代碼:
ComputableLiveDat.java
    @VisibleForTesting
    final Runnable mRefreshRunnable = new Runnable() {
        @WorkerThread
        @Override
        public void run() {
            boolean computed;
            do {
                ... // 其他代碼
                mLiveData.postValue(value);
                ... // 其他代碼
            } while (computed && mInvalid.get());
        }
    };

關(guān)鍵代碼為postValue(value)幌羞,通過(guò)LiveData的postValue方法設(shè)置數(shù)據(jù)寸谜,并在外部監(jiān)聽(tīng)

對(duì)于RxPagedListBuilder,通過(guò)buildObservable方法創(chuàng)建一個(gè)Observable属桦,將其他邏輯代碼放到一個(gè)ObservableOnSubscribe類中實(shí)現(xiàn)熊痴,subscribe回調(diào)方法中實(shí)現(xiàn)PageList的構(gòu)造,代碼如下:

    private PagedList<Value> createPagedList() {
            ... // 其他代碼
            do {
                ... // 其他代碼

                mList = new PagedList.Builder<>(mDataSource, mConfig)
                        .setNotifyExecutor(mNotifyExecutor)
                        .setFetchExecutor(mFetchExecutor)
                        .setBoundaryCallback(mBoundaryCallback)
                        .setInitialKey(initializeKey)
                        .build();
            } while (mList.isDetached());
            return mList;
        }

最終結(jié)果還是為了構(gòu)造一個(gè)PagedList聂宾。

兩種方式最終都是為了構(gòu)造一個(gè)PagedList果善,一個(gè)通過(guò)ComputableLiveData包裹一層通過(guò)在Runnable中調(diào)用LiveData的postValue方法通知值更新;另一個(gè)是借助RxJava的線程切換系谐,創(chuàng)建后通過(guò)設(shè)置觀察者取得結(jié)果巾陕。
參考文章:
https://enginebai.com/2019/04/22/android-paging-part1/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末讨跟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子鄙煤,更是在濱河造成了極大的恐慌晾匠,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件梯刚,死亡現(xiàn)場(chǎng)離奇詭異凉馆,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)亡资,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門澜共,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人锥腻,你說(shuō)我怎么就攤上這事嗦董。” “怎么了瘦黑?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵京革,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我供璧,道長(zhǎng),這世上最難降的妖魔是什么冻记? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任睡毒,我火速辦了婚禮,結(jié)果婚禮上冗栗,老公的妹妹穿的比我還像新娘演顾。我一直安慰自己,他們只是感情好隅居,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布钠至。 她就那樣靜靜地躺著,像睡著了一般胎源。 火紅的嫁衣襯著肌膚如雪棉钧。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天涕蚤,我揣著相機(jī)與錄音宪卿,去河邊找鬼。 笑死万栅,一個(gè)胖子當(dāng)著我的面吹牛佑钾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播烦粒,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼休溶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起兽掰,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤芭碍,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后禾进,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體豁跑,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年泻云,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了艇拍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宠纯,死狀恐怖卸夕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情婆瓜,我是刑警寧澤快集,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站廉白,受9級(jí)特大地震影響个初,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜猴蹂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一院溺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧磅轻,春花似錦珍逸、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至撮躁,卻和暖如春漱病,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背把曼。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工缨称, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祝迂。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓睦尽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親型雳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子当凡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345