轉(zhuǎn)載請(qǐng)標(biāo)明出處http://www.reibang.com/p/b9c7a6e3e8d2
前言:最近有個(gè)需求腺阳,要考慮到系統(tǒng)資源以及網(wǎng)絡(luò)請(qǐng)求的效率,需要做一個(gè)類(lèi)似于現(xiàn)在市面上那種列表頁(yè)面可以往上滑動(dòng)不斷加載item的效果穿香,想著自己寫(xiě)邏輯亭引,對(duì)控件recycleview的滑動(dòng)到底部的事件進(jìn)行判定之后請(qǐng)求數(shù)據(jù),再去對(duì)adapter進(jìn)行數(shù)據(jù)的判斷增加然后視圖刷新皮获。一籮筐下來(lái)覺(jué)得好麻煩啊焙蚓,就去Google了一下,發(fā)現(xiàn)官方提供了Paging庫(kù)來(lái)處理這個(gè)場(chǎng)景。由于這個(gè)框架也是運(yùn)用到LiveData购公,不熟悉的朋友可以先看一下之前LiveData的文章萌京。
1.Paging
Paging是一個(gè)官方提供的分頁(yè)庫(kù)。使用這個(gè)庫(kù)宏浩,我們只需要關(guān)心數(shù)據(jù)知残,分頁(yè)和視圖是不需要我們?nèi)リP(guān)心的,這個(gè)庫(kù)會(huì)幫我們實(shí)現(xiàn)比庄。在這個(gè)庫(kù)求妹,最重要的就是關(guān)鍵組件是PagedList類(lèi)。而且一般來(lái)說(shuō)佳窑,這個(gè)庫(kù)都會(huì)搭配著RXjava2或者LiveData來(lái)使用制恍,友好的處理控件和數(shù)據(jù)的生命周期。本文的例子將會(huì)使用LiveData神凑。
2.Paging的配合
2.1.數(shù)據(jù)
列表數(shù)據(jù)的來(lái)源净神,可分為本地?cái)?shù)據(jù)和網(wǎng)絡(luò)數(shù)據(jù),本地?cái)?shù)據(jù)官方是建議使用Room持久庫(kù)來(lái)整理數(shù)據(jù)的耙厚,而網(wǎng)絡(luò)數(shù)據(jù)强挫,可以使用自己定義的數(shù)據(jù)源工廠(chǎng),本文例子也將使用自定義數(shù)據(jù)源工廠(chǎng)薛躬。
22.界面
分頁(yè)展示,這個(gè)庫(kù)需要搭配recyclerview來(lái)進(jìn)行展示呆细,recyclerview也會(huì)有Paging提供的特殊adapter類(lèi)型宝。
3.使用
二話(huà)不多說(shuō),項(xiàng)目依賴(lài)走起絮爷。
implementation "androidx.paging:paging-runtime:2.1.2"
testImplementation "androidx.paging:paging-common:2.1.2"
implementation "androidx.paging:paging-rxjava2:2.1.2"
分頁(yè)列表的實(shí)現(xiàn)趴酣,界面由recyclerview來(lái)實(shí)現(xiàn),這里recyclerview要注意坑夯,要繼承Paging庫(kù)提供的PagedListAdapter類(lèi)岖寞,這是實(shí)現(xiàn)分頁(yè)效果的關(guān)鍵。
public class AppleAdapter extends PagedListAdapter<apple, AppleAdapter.MyViewHolder> {
使用這個(gè)類(lèi)我們不需要再重寫(xiě)getItemCount方法柜蜈,PagedListAdapter自己重寫(xiě)了getItemCount仗谆,我們只需要通過(guò)設(shè)置DiffUtil來(lái)使得它可以對(duì)數(shù)據(jù)差異進(jìn)行判斷∈缏模可以通過(guò)item特有屬性的對(duì)別或者item的整個(gè)對(duì)象的對(duì)比來(lái)得出差異隶垮,從而決定是否要更新到列表中去。
public AppleAdapter() {
super(DIFF_CALLBACK);
}
public static final DiffUtil.ItemCallback<apple> DIFF_CALLBACK = new DiffUtil.ItemCallback<apple>() {
@Override
public boolean areItemsTheSame(@NonNull apple oldApple, @NonNull apple newApple) {
// User properties may have changed if reloaded from the DB, but ID is fixed
return oldApple.getId() == newApple.getId();
}
@Override
public boolean areContentsTheSame(@NonNull apple oldApple, @NonNull apple newApple) {
// NOTE: if you use equals, your object must properly override Object#equals()
// Incorrectly returning false here will result in too many animations.
return newApple.equals(newApple);
}
};
其次就是數(shù)據(jù)了秘噪,這里我自己定義了數(shù)據(jù)源工廠(chǎng)類(lèi)狸吞。配合了LiveData進(jìn)行使用。先來(lái)看一下數(shù)據(jù)源工廠(chǎng)類(lèi)。
private class MyAppleSourceFactory extends DataSource.Factory<Integer, apple> {
private MutableLiveData<MyAppleSource> sourceMutableLiveData = new MutableLiveData<>();
private MyAppleSource source;
@NonNull
@Override
public DataSource<Integer, apple> create() {
source = new MyAppleSource();
//查看Google的文檔也沒(méi)看明白這個(gè)liveData是為什么
//但是猜測(cè)可能是想利用liveData對(duì)-生命周期進(jìn)行監(jiān)聽(tīng),有懂的朋友可以評(píng)論不吝賜教蹋偏。
sourceMutableLiveData.postValue(source);
return source;
}
}
private class MyAppleSource extends ItemKeyedDataSource<Integer, apple> {
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback callback) {
List<apple> items = getMoreMyApple(0);
callback.onResult(items);
}
@Override
public void loadAfter(@NonNull LoadParams params, @NonNull LoadCallback callback) {
List<apple> items = getMoreMyApple((Integer) params.key);
callback.onResult(items);
}
@Override
public void loadBefore(@NonNull LoadParams params, @NonNull LoadCallback callback) {
}
@NonNull
@Override
public Integer getKey(@NonNull Apple item) {
return item.getId();
}
}
工廠(chǎng)類(lèi)內(nèi)部實(shí)例化了一個(gè)Source類(lèi)便斥,這個(gè)類(lèi)我這里是繼承了ItemKeyedDataSource,他是規(guī)定整個(gè)分頁(yè)是由item的某一個(gè)屬性去獲取數(shù)據(jù)的威始。官方還提供了其余2個(gè)Source類(lèi):PageKeyedDataSource枢纠,PositionalDataSource。解決各自的特定場(chǎng)景字逗,需要的朋友可以自己Google了解一下京郑。
然后看一下控件和工廠(chǎng)類(lèi)數(shù)據(jù)的初始化,我這里是用了Viewmodel來(lái)實(shí)現(xiàn)demo的葫掉。
public LiveData<PagedList<Apple>> appleMutableLiveData;
public void initMyApple() {
MyAppleSourceFactory appleSourceFactory = new MyAppleSourceFactory();
myAppleSource = appleSourceFactory.create();
//pageList的LiveData由activity這個(gè)UI層去進(jìn)行監(jiān)聽(tīng)些举。
appleMutableLiveData = new LivePagedListBuilder(appleSourceFactory, 10).build();
}
最后看一下Activity,
appleMyBinding = DataBindingUtil.setContentView(this, R.layout.activity_apple_my);
//蘋(píng)果列表初始化俭厚,其實(shí)就是正常的recyclerview的管理器布局器設(shè)置
initRVApple();
myAppleViewModel = ViewModelProviders.of(this).get(AppleViewModel.class);
//PagedList的LiveData初始化
myAppleViewModel.initMyApple();
myAppleViewModel.appleMutableLiveData.observe(this, appleVOS -> {
//停止加載户魏,通知回調(diào)
myAppleViewModel.invalidateDataSource();
//游戲列表數(shù)據(jù)變化監(jiān)聽(tīng),其實(shí)這個(gè)操作相當(dāng)于向adapter傳遞數(shù)據(jù)的過(guò)程。
//這也是一個(gè)LiveData連接PagedListAdapter的過(guò)程
//PagedListAdapter會(huì)自動(dòng)處理分頁(yè)差異更新
appleAdapter.submitList(appleVOS);
});
這里關(guān)鍵其實(shí)是appleAdapter.submitList(appleVOS)挪挤。是由于這個(gè)方法叼丑,recyclerview和數(shù)據(jù)搭上了線(xiàn)。
到這里代碼就寫(xiě)的差不多了扛门。activity中recyclerview的初始化鸠信,數(shù)據(jù)源工廠(chǎng)類(lèi)的自定義,viewmodel中的數(shù)據(jù)源工廠(chǎng)類(lèi)初始化论寨。通過(guò)這一系列操作就可以實(shí)現(xiàn)分頁(yè)的效果星立。
在不知道他的實(shí)現(xiàn)原理的情況下,我們大膽猜測(cè)一下葬凳,整個(gè)流程是這樣的绰垂,在ViewModel中LiveData通常是去通過(guò)model層獲取數(shù)據(jù)設(shè)置數(shù)據(jù)到LiveData對(duì)象中的,也就是當(dāng)分頁(yè)開(kāi)始請(qǐng)求數(shù)據(jù)的時(shí)候火焰,數(shù)據(jù)來(lái)源于工廠(chǎng)類(lèi)劲装,它實(shí)際上會(huì)走Source類(lèi)的loadInitial方法設(shè)置數(shù)據(jù),之后界面每次數(shù)據(jù)不夠顯示了昌简,就會(huì)調(diào)用loadAfter的方法去設(shè)置數(shù)據(jù)占业。實(shí)際中g(shù)etMoreMyApple也是對(duì)應(yīng)了Model層,是一個(gè)關(guān)于數(shù)據(jù)的網(wǎng)絡(luò)請(qǐng)求方法江场,根據(jù)數(shù)據(jù)的id去請(qǐng)求數(shù)據(jù)纺酸。數(shù)據(jù)設(shè)置到LiveData中了,通過(guò)Adapter.submitList綁定到界面址否。那么關(guān)鍵就在于餐蔬,裝有PagedList的LiveData是如何得到的碎紊。咱們來(lái)看一下源碼。
4.原理簡(jiǎn)析
官方寫(xiě)法是樊诺,在activity新建時(shí)viewmodel實(shí)例化仗考,一起調(diào)用LivePagedListBuilder而獲得一個(gè)LiveData對(duì)象,這個(gè)對(duì)象中包含了PagedList對(duì)象词爬。這個(gè)LiveData是如何獲得秃嗜,咱們一層一層剝開(kāi)看看。
appleMutableLiveData = new LivePagedListBuilder(appleSourceFactory, 10).build();
這個(gè)build到底做了什么可以得到一個(gè)LiveData顿膨,點(diǎn)開(kāi)源碼瞧瞧锅锨。
public LiveData<PagedList<Value>> build() {
return create(mInitialLoadKey, mConfig, mBoundaryCallback, mDataSourceFactory,
ArchTaskExecutor.getMainThreadExecutor(), mFetchExecutor);
}
他返回的是一個(gè)create方法執(zhí)行得到的結(jié)果×滴郑看看create方法必搞。
private static <Key, Value> LiveData<PagedList<Value>> create(
@Nullable final Key initialLoadKey,
@NonNull final PagedList.Config config,
@Nullable final PagedList.BoundaryCallback boundaryCallback,
@NonNull final DataSource.Factory<Key, Value> dataSourceFactory,
@NonNull final Executor notifyExecutor,
@NonNull final Executor fetchExecutor) {
return new ComputableLiveData<PagedList<Value>>(fetchExecutor) {
@Nullable
private PagedList<Value> mList;
@Nullable
private DataSource<Key, Value> mDataSource;
private final DataSource.InvalidatedCallback mCallback =
new DataSource.InvalidatedCallback() {
@Override
public void onInvalidated() {
invalidate();
}
};
@SuppressWarnings("unchecked") // for casting getLastKey to Key
@Override
protected PagedList<Value> compute() {
@Nullable Key initializeKey = initialLoadKey;
if (mList != null) {
initializeKey = (Key) mList.getLastKey();
}
do {
if (mDataSource != null) {
mDataSource.removeInvalidatedCallback(mCallback);
}
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached());
return mList;
}
}.getLiveData();
}
create方法返回的則是一個(gè)ComputableLiveData類(lèi)的實(shí)例化對(duì)象的getLiveData的值,看看這個(gè)ComputableLiveData類(lèi)構(gòu)造方法以及這個(gè)getLiveData方法囊咏。
public ComputableLiveData(@NonNull Executor executor) {
mExecutor = executor;
mLiveData = new LiveData<T>() {
@Override
protected void onActive() {
mExecutor.execute(mRefreshRunnable);
}
};
}
@NonNull
public LiveData<T> getLiveData() {
return mLiveData;
}
原來(lái)這一系列下來(lái)是一個(gè)LiveData對(duì)象實(shí)例化以及返回的過(guò)程恕洲。而且當(dāng)這個(gè)liveData啟用的時(shí)候會(huì)在線(xiàn)程池中運(yùn)行一個(gè)子線(xiàn)程。咱們看看這個(gè)子線(xiàn)程了干了些什么梅割。
final Runnable mRefreshRunnable = new Runnable() {
@WorkerThread
@Override
public void run() {
boolean computed;
do {
computed = false;
// compute can happen only in 1 thread but no reason to lock others.
if (mComputing.compareAndSet(false, true)) {
// as long as it is invalid, keep computing.
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
value = compute();
}
if (computed) {
mLiveData.postValue(value);
}
} finally {
// release compute lock
mComputing.set(false);
}
}
// check invalid after releasing compute lock to avoid the following scenario.
// Thread A runs compute()
// Thread A checks invalid, it is false
// Main thread sets invalid to true
// Thread B runs, fails to acquire compute lock and skips
// Thread A releases compute lock
// We've left invalid in set state. The check below recovers.
} while (computed && mInvalid.get());
}
};
這個(gè)子線(xiàn)程中對(duì)LiveData進(jìn)行了postValue霜第,這下子就清楚,這就是build方法返回的裝有PagedList的liveData數(shù)據(jù)變化户辞,UI也會(huì)變化了泌类。而這個(gè)postValue的值來(lái)源于compute抽象方法的,咱們回頭看看在LivePagedListBuilder中實(shí)例化的ComputableLiveData對(duì)象的compute方法具體是怎么實(shí)現(xiàn)的底燎。
@Override
protected PagedList<Value> compute() {
@Nullable Key initializeKey = initialLoadKey;
if (mList != null) {
initializeKey = (Key) mList.getLastKey();
}
do {
if (mDataSource != null) {
mDataSource.removeInvalidatedCallback(mCallback);
}
mDataSource = dataSourceFactory.create();
mDataSource.addInvalidatedCallback(mCallback);
mList = new PagedList.Builder<>(mDataSource, config)
.setNotifyExecutor(notifyExecutor)
.setFetchExecutor(fetchExecutor)
.setBoundaryCallback(boundaryCallback)
.setInitialKey(initializeKey)
.build();
} while (mList.isDetached());
return mList;
}
他post的value原來(lái)是來(lái)自于這個(gè)方法新建的PagedList對(duì)象末誓,而他的值就是來(lái)源于dataSourceFactory的mDataSource對(duì)象的。這個(gè)factory對(duì)象就是我們build的時(shí)候傳入的自定義數(shù)據(jù)源工廠(chǎng)類(lèi)了书蚪。然后再來(lái)看看這個(gè)PagedList是怎么build生成一個(gè)PagedList對(duì)象的。
public PagedList<Value> build() {
// TODO: define defaults, once they can be used in module without android dependency
if (mNotifyExecutor == null) {
throw new IllegalArgumentException("MainThreadExecutor required");
}
if (mFetchExecutor == null) {
throw new IllegalArgumentException("BackgroundThreadExecutor required");
}
//noinspection unchecked
return PagedList.create(
mDataSource,
mNotifyExecutor,
mFetchExecutor,
mBoundaryCallback,
mConfig,
mInitialKey);
}
}
由PagedList類(lèi)create方法實(shí)例化的迅栅,往下看殊校。
static <K, T> PagedList<T> create(@NonNull DataSource<K, T> dataSource,
@NonNull Executor notifyExecutor,
@NonNull Executor fetchExecutor,
@Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config,
@Nullable K key) {
if (dataSource.isContiguous() || !config.enablePlaceholders) {
int lastLoad = ContiguousPagedList.LAST_LOAD_UNSPECIFIED;
if (!dataSource.isContiguous()) {
//noinspection unchecked
dataSource = (DataSource<K, T>) ((PositionalDataSource<T>) dataSource)
.wrapAsContiguousWithoutPlaceholders();
if (key != null) {
lastLoad = (Integer) key;
}
}
ContiguousDataSource<K, T> contigDataSource = (ContiguousDataSource<K, T>) dataSource;
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);
}
}
看著源碼,這個(gè)source對(duì)象通過(guò)一系列判斷最后會(huì)轉(zhuǎn)為兩種PagedList類(lèi)读存,不過(guò)ContiguousDataSource比較特別为流,我們點(diǎn)進(jìn)去看看。
ContiguousDataSource<Integer, T> wrapAsContiguousWithoutPlaceholders() {
return new ContiguousWithoutPlaceholdersWrapper<>(this);
}
ContiguousWithoutPlaceholdersWrapper(
@NonNull PositionalDataSource<Value> source) {
mSource = source;
}
ContiguousPagedList(
@NonNull ContiguousDataSource<K, V> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<V> boundaryCallback,
@NonNull Config config,
final @Nullable K key,
int lastLoad) {
super(new PagedStorage<V>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
mLastLoad = lastLoad;
if (mDataSource.isInvalid()) {
detach();
} else {
mDataSource.dispatchLoadInitial(key,
mConfig.initialLoadSizeHint,
mConfig.pageSize,
mConfig.enablePlaceholders,
mMainThreadExecutor,
mReceiver);
}
mShouldTrim = mDataSource.supportsPageDropping()
&& mConfig.maxSize != Config.MAX_SIZE_UNBOUNDED;
}
TiledPagedList(@NonNull PositionalDataSource<T> dataSource,
@NonNull Executor mainThreadExecutor,
@NonNull Executor backgroundThreadExecutor,
@Nullable BoundaryCallback<T> boundaryCallback,
@NonNull Config config,
int position) {
super(new PagedStorage<T>(), mainThreadExecutor, backgroundThreadExecutor,
boundaryCallback, config);
mDataSource = dataSource;
final int pageSize = mConfig.pageSize;
mLastLoad = position;
if (mDataSource.isInvalid()) {
detach();
} else {
final int firstLoadSize =
(Math.max(mConfig.initialLoadSizeHint / pageSize, 2)) * pageSize;
final int idealStart = position - firstLoadSize / 2;
final int roundedPageStart = Math.max(0, idealStart / pageSize * pageSize);
mDataSource.dispatchLoadInitial(true, roundedPageStart, firstLoadSize,
pageSize, mMainThreadExecutor, mReceiver);
}
}
這兩個(gè)PagedList方法邏輯相似一上來(lái)就會(huì)對(duì)DataSource再判斷让簿,然后決定是否走PositionalDataSource的dispatchLoadInitial方法敬察。
這個(gè)方法看著好眼熟,會(huì)讓人想起自定義的source類(lèi)不是嗎尔当。咱們往下看莲祸。
final void dispatchLoadInitial(boolean acceptCount,
int requestedStartPosition, int requestedLoadSize, int pageSize,
@NonNull Executor mainThreadExecutor, @NonNull PageResult.Receiver<T> receiver) {
LoadInitialCallbackImpl<T> callback =
new LoadInitialCallbackImpl<>(this, acceptCount, pageSize, receiver);
LoadInitialParams params = new LoadInitialParams(
requestedStartPosition, requestedLoadSize, pageSize, acceptCount);
loadInitial(params, callback);
// If initialLoad's callback is not called within the body, we force any following calls
// to post to the UI thread. This constructor may be run on a background thread, but
// after constructor, mutation must happen on UI thread.
callback.mCallbackHelper.setPostExecutor(mainThreadExecutor);
}
這個(gè)方法蹂安,直接就調(diào)用我們自定義source類(lèi)的loadInitial方法了。就可以拿到自定義請(qǐng)求方法得到的PagedList了锐帜,還有l(wèi)oadAfter和loadBefore的調(diào)用過(guò)程這里就不再深入了田盈。
接下來(lái)來(lái)看一下,這個(gè)source中的關(guān)于數(shù)據(jù)加載時(shí)機(jī)的回調(diào)缴阎,是怎么配合PagedListAdapter的允瞧。看看Adapter蛮拔。
public AppleAdapter() {
super(DIFF_CALLBACK);
}
protected PagedListAdapter(@NonNull DiffUtil.ItemCallback<T> diffCallback) {
mDiffer = new AsyncPagedListDiffer<>(this, diffCallback);
mDiffer.addPagedListListener(mListener);
}
private final AsyncPagedListDiffer.PagedListListener<T> mListener =
new AsyncPagedListDiffer.PagedListListener<T>() {
@Override
public void onCurrentListChanged(
@Nullable PagedList<T> previousList, @Nullable PagedList<T> currentList) {
PagedListAdapter.this.onCurrentListChanged(currentList);
PagedListAdapter.this.onCurrentListChanged(previousList, currentList);
}
};
這里先記住述暂,它內(nèi)部會(huì)實(shí)例化一個(gè)AsyncPagedListDiffer對(duì)象并且賦予回調(diào)以及設(shè)置PagedListListener。
然后我們來(lái)看一下PagedListAdapter的submit方法建炫。因?yàn)槭且蕾?lài)這個(gè)方法實(shí)現(xiàn)數(shù)據(jù)和界面的綁定的畦韭。
appleAdapter.submitList(apples);
public void submitList(@Nullable PagedList<T> pagedList) {
mDiffer.submitList(pagedList);
}
發(fā)現(xiàn)是調(diào)用了AsyncPagedListDiffer這個(gè)類(lèi)的submitList方法。往里面深處翻發(fā)現(xiàn)
public void submitList(@Nullable final PagedList<T> pagedList,
@Nullable final Runnable commitCallback) {
這里有個(gè)關(guān)鍵方法踱卵,onCurrentListChanged(previous, null, commitCallback);
private void onCurrentListChanged(
@Nullable PagedList<T> previousList,
@Nullable PagedList<T> currentList,
@Nullable Runnable commitCallback) {
for (PagedListListener<T> listener : mListeners) {
listener.onCurrentListChanged(previousList, currentList);
}
if (commitCallback != null) {
commitCallback.run();
}
}
實(shí)際上他就是調(diào)用了上面PagedListListener的onCurrentListChanged廊驼,走的是PagedListAdapter的onCurrentListChanged方法的⊥锷埃看了一下源碼注釋和翻了一下文檔妒挎,這是一個(gè)當(dāng)前PagedList更新時(shí)調(diào)用的兩個(gè)方法。
到這里就差不多知道整個(gè)流程了西饵。通過(guò)LivePagedListBuilder拿到工廠(chǎng)類(lèi)的Source酝掩,通過(guò)一系列回調(diào)走到我們自定義loadInitial方法,由此拿到PageList對(duì)象眷柔,將一個(gè)PagedList對(duì)象傳遞給Adapter期虾,當(dāng)頁(yè)面變化,調(diào)用AsyncPagedListDiffer的onCurrentListChanged驯嘱,就會(huì)觸發(fā)Adapter的onCurrentListChanged镶苞。
5.總結(jié)
剛開(kāi)始用的時(shí)候,由于都不知道其原理鞠评,只能先一邊看文檔一邊寫(xiě)個(gè)demo茂蚓,功能實(shí)現(xiàn)了,就是搞不明白為什么要寫(xiě)factory為什么要寫(xiě)source剃幌,為什么是LivePagedListBuilder去build拿到LiveData的聋涨,為什么通過(guò)submitList就可以讓數(shù)據(jù)綁定到控件,然后隨著控件滑動(dòng)就可以觸發(fā)source的loadAfter了负乡。通過(guò)翻看源碼結(jié)合注釋以及翻看文檔牍白。終于懂得一二,總算知道了為什么可以通過(guò)這樣一個(gè)流程實(shí)現(xiàn)一個(gè)分頁(yè)的效果了抖棘。