目錄:
需求來(lái)源
Paging運(yùn)作流程
Paging三大部件
如何使用
三種DataSource對(duì)比
LivePagedListBuilder本慕、RxPagedListBuilder對(duì)比
一、需求來(lái)源:
為方便實(shí)現(xiàn)上拉加載恒水、簡(jiǎn)化代碼效拭、上拉加載邏輯可配置...總之就是為了方便
Paging
出現(xiàn)前暂吉,上拉加載觸發(fā)一般是通過(guò):
- 監(jiān)聽(tīng)RecyclerView的滾動(dòng)事件,判斷RecyclerView是否滾動(dòng)到底部
- 處理Adapter的
onBindViewHolder
方法缎患,根據(jù)位置與數(shù)量判斷當(dāng)前位置item是否該觸發(fā)上拉加載 - 或者其他方式...
如方式一:
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)部PagedList
向DataSource
拉取數(shù)據(jù)摇肌,獲得數(shù)據(jù)后通過(guò)DiffUtils
對(duì)比得到最終數(shù)據(jù)集
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ù)
3. DataSource
負(fù)責(zé)實(shí)現(xiàn)數(shù)據(jù)載入實(shí)現(xiàn)挣输,常用子類如下:
-
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.
-
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
-
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)在于LoadInitialParams
和LoadRangeParams
中參數(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ù)只取了requestedLoadSize
和placeholdersEnabled
這兩個(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ù)
- 只要正確配置
previousPageKey
和nextPageKey
(上述代碼中的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)方式
- 如何使用
/**
* 最終返回的是 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) }
- 值回調(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/