??樓主最近在學(xué)習(xí)Google爸爸最新的組件--Jetpack,說實(shí)話,看文檔誰都看得懂,但是動(dòng)起手來就徹底懵逼。就比如說,今天要介紹的paging的基本使用,樓主就踩了兩天的坑。這個(gè)過程中比較麻煩的一點(diǎn)就是,官方文檔和網(wǎng)上大部分資料都是用kotlin語言寫的,可惜我不懂kotlin,只會(huì)寫一點(diǎn)小Java。
??在閱讀本文之前,我默認(rèn)大家都了解jetpack是什么。因此本文不再介紹jetpack,注重介紹paging怎么使用,本文會(huì)大量的貼出Demo代碼,供大家理解和參考,同時(shí)也會(huì)介紹這其中我踩的坑。
??本文參考資料:
??在閱讀本文之前,最好是閱讀過上面兩篇官方文檔。其中第一篇介紹的是paging的基本使用,第二篇是對(duì)第一篇的補(bǔ)充,主要介紹怎么自定義DataSource。
1. Demo概述
??在正式介紹paging 的使用之前,我們先來看看最終的效果是怎么樣的。

??效果是非常的簡(jiǎn)單,就是有一個(gè)
RecyclerView在顯示數(shù)據(jù),然后我們向下滑動(dòng),滑動(dòng)到一定位置,paging會(huì)自動(dòng)請(qǐng)求數(shù)據(jù),這也就是分頁請(qǐng)求的效果.paging的目的就是實(shí)現(xiàn)分頁加載,之前我們都是自己實(shí)現(xiàn)分頁,這次是Google爸爸為我們實(shí)現(xiàn)。不得不說,jetpack真香!
2. 準(zhǔn)備components
??要想使用paging,我們得準(zhǔn)備幾個(gè)東西(當(dāng)然,導(dǎo)依賴這種基本操作肯定是必須的,我就不介紹了哈)。
- DataSource:顧名思義,數(shù)據(jù)源,獲取數(shù)據(jù)是通過它實(shí)現(xiàn)的。
- PagedList: 數(shù)據(jù)源獲取的數(shù)據(jù)最終靠
PagedList來承載。對(duì)于PagedList,我們可以這樣來理解,它就是一頁數(shù)據(jù)的集合。每請(qǐng)求一頁,就是新的一個(gè)PagedList對(duì)象。- PagedListAdapter:這個(gè)Adapter就是一個(gè)
RecyclerView的Adapter。不過我們?cè)谑褂?code>paging實(shí)現(xiàn)RecyclerView的分頁加載效果,不能直接繼承RecyclerView的Adapter,而是需要繼承PagedListAdapter。
3. 自定義DataSource
??說實(shí)話,自定義DataSource沒有什么難度,但是我為什么要單獨(dú)提出來呢?首先官方文檔上,實(shí)現(xiàn)的是ItemKeyedDataSource,而我這里實(shí)現(xiàn)的是PositionalDataSource。雖然差別不是很大,但是還是有點(diǎn)區(qū)別,因?yàn)樵谶@里我踩過兩個(gè)坑。我相信,通過這兩個(gè)坑,以后遇到類似的問題,會(huì)找到解決思路。
??在貼代碼之前,我們先來看看實(shí)現(xiàn)PositionalDataSource需要注意的地方。
??首先,我們得了解一下需要實(shí)現(xiàn)的兩個(gè)方法:
| 方法名 | 含義 |
|---|---|
| loadInitial | 加載初始化數(shù)據(jù),可以這么來理解,加載的是第一頁的數(shù)據(jù)。形象的說,當(dāng)我們第一次打開頁面,需要回調(diào)此方法來獲取數(shù)據(jù)。 |
| loadRange | 當(dāng)有了初始化數(shù)據(jù)之后,滑動(dòng)的時(shí)候如果需要加載數(shù)據(jù)的話,會(huì)調(diào)用此方法。 |
??現(xiàn)在,我們來看看最終實(shí)現(xiàn)的代碼。
public class ConcertDataSource extends PositionalDataSource<Concert> {
private final Retrofit mRetrofit = new Retrofit.Builder()
.baseUrl("http://api.apiopen.top/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
private final Service mService;
public ConcertDataSource() {
mService = mRetrofit.create(Service.class);
}
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull final LoadInitialCallback<Concert> callback) {
// fetchItem(0, params.pageSize, new Callback() {
// @Override
// public void onResult(Bean value) {
// callback.onResult(value.getResult(), 0, 2000);
// }
// });
callback.onResult(fetchItems(0, 20), 0, 2000);
}
@Override
public void loadRange(@NonNull LoadRangeParams params, @NonNull final LoadRangeCallback<Concert> callback) {
// fetchItem(params.startPosition, params.loadSize, new Callback() {
// @Override
// public void onResult(Bean value) {
// callback.onResult(value.getResult());
// }
// });
callback.onResult(fetchItems(params.startPosition, params.loadSize));
}
private List<Concert> fetchItems(int startPosition, int pageSize) {
List<Concert> list = new ArrayList<>();
for (int i = startPosition; i < startPosition + pageSize; i++) {
Concert concert = new Concert();
concert.setAuthor("author = " + i);
concert.setContent("content = " + i);
concert.setTitle("title = " + i);
list.add(concert);
}
return list;
}
private void fetchItem(final int startPosition, int size, final Callback callback) {
mService.getCall(startPosition, size).subscribe(new Observer<Bean>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Bean value) {
callback.onResult(value);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
private interface Callback {
void onResult(Bean value);
}
}
??然后,我結(jié)合上面的代碼來介紹一下我踩過的兩個(gè)坑(其實(shí)這兩個(gè)坑可以歸并為一個(gè)坑,因?yàn)槎际且粋€(gè)原因?qū)е?。
??我先來描述我之前我遇到的問題,在第一次打開界面時(shí),我發(fā)現(xiàn),RecyclerView沒有顯示任何的數(shù)據(jù),同時(shí)經(jīng)過debug,發(fā)現(xiàn)loadInitial方法被回調(diào)到的,但是最終Adapter里面的itemCount為0。這是什么原因?qū)е碌哪??我們來看看現(xiàn)在的loadInitial方法的實(shí)現(xiàn):
@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull final LoadInitialCallback<Concert> callback) {
// fetchItem(0, params.pageSize, new Callback() {
// @Override
// public void onResult(Bean value) {
// callback.onResult(value.getResult(), 0, 2000);
// }
// });
callback.onResult(fetchItems(0, 20), 0, 2000);
}
??我們通過LoadInitialCallback的onResult方法一共傳遞了三個(gè)參數(shù),這三個(gè)參數(shù)分別表示什么意思?我們先來LoadInitialCallback這個(gè)類吧。
public abstract static class LoadInitialCallback<T> {
public abstract void onResult(@NonNull List<T> data, int position, int totalCount);
public abstract void onResult(@NonNull List<T> data, int position);
}
??LoadInitialCallback一共有兩個(gè)onResult方法。而我這里出現(xiàn)了RecyclerView不顯示任何數(shù)據(jù),是因?yàn)槲艺{(diào)用得是兩個(gè)參數(shù)的onResult方法,導(dǎo)致了totalCount為0,所以顯示不出任何數(shù)據(jù)。
??第二個(gè)問題就是,數(shù)據(jù)顯示出來了,但是始終顯示一頁的數(shù)據(jù),也就是說loadRange方法不會(huì)調(diào)用。其實(shí)也是totalCount導(dǎo)致的,當(dāng)時(shí)我解決了第一個(gè)的問題時(shí),直接給totalCount傳遞的是List的size(假設(shè)為N條)。也就是說,PositionalDataSource最終一共只會(huì)獲取N條的數(shù)據(jù),因?yàn)榈谝豁摰拇笮∏『镁偷扔贜,所以不會(huì)調(diào)用loadRange方法去加載更多的數(shù)據(jù)。
3. 創(chuàng)建ViewModel
??相信比較熟悉JetPack的同學(xué)都知道,在Jetpack的架構(gòu)里面,推薦每個(gè)頁面持有一個(gè)ViewModel對(duì)象,以保證數(shù)據(jù)的正確性和避免其他的問題。本文就不介紹ViewModel,不太了解的同學(xué)可以看一下的官方文檔,這是鏈接: ViewModel Overview Part of Android Jetpack。
??之前,我們已經(jīng)成功的自定義一個(gè)DataSource,現(xiàn)在我們來看看怎么使用ViewModel跟DataSource連接起來。
??首先,我們得創(chuàng)建工廠類,用來創(chuàng)建DataSource對(duì)象,這在Jetpack架構(gòu)里面是基本需要。
public class ConcertFactory extends DataSource.Factory<Integer, Concert> {
private MutableLiveData<ConcertDataSource> mSourceLiveData =
new MutableLiveData<>();
@Override
public DataSource<Integer, Concert> create() {
ConcertDataSource concertDataSource = new ConcertDataSource();
mSourceLiveData.postValue(concertDataSource);
return concertDataSource;
}
}
??至于為什么需要一個(gè)MutableLiveData將DataSource包裝一下,這個(gè)我也不是很了解,官方文檔就是這么寫的(可能是讓DataSource監(jiān)聽Activity或者Fragment的生命周期,以保證只在頁面活躍狀態(tài)(onStart或者onResume)獲取數(shù)據(jù))。
??其次,就是創(chuàng)建對(duì)應(yīng)的ViewModel:
public class ConcertViewModel extends ViewModel {
private final LiveData<PagedList<Concert>> convertList;
private DataSource<Integer, Concert> concertDataSource;
public ConcertViewModel() {
ConcertFactory concertFactory = new ConcertFactory();
concertDataSource = concertFactory.create();
convertList = new LivePagedListBuilder<>(concertFactory, 20).build();
}
public void invalidateDataSource() {
concertDataSource.invalidate();
}
public LiveData<PagedList<Concert>> getConvertList() {
return convertList;
}
}
4. RecyclerView
??首先,我們來看一下Adapter的定義:
public class RecyclerAdapter extends PagedListAdapter<Concert, RecyclerAdapter.RecyclerViewHolder> {
protected RecyclerAdapter() {
super(DIFF_CALLBACK);
}
@NonNull
@Override
public RecyclerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recyclerview, parent, false);
return new RecyclerViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerViewHolder holder, int position) {
Concert concert = getItem(position);
if (concert != null) {
holder.mTitleTextView.setText(concert.getTitle());
holder.mAuthorTextView.setText(concert.getAuthor());
holder.mContentTextView.setText(concert.getContent());
}
}
public static class RecyclerViewHolder extends RecyclerView.ViewHolder {
TextView mTitleTextView;
TextView mAuthorTextView;
TextView mContentTextView;
public RecyclerViewHolder(View itemView) {
super(itemView);
mTitleTextView = itemView.findViewById(R.id.title);
mAuthorTextView = itemView.findViewById(R.id.author);
mContentTextView = itemView.findViewById(R.id.content);
}
}
private static DiffUtil.ItemCallback<Concert> DIFF_CALLBACK =
new DiffUtil.ItemCallback<Concert>() {
@Override
public boolean areItemsTheSame(Concert oldConcert, Concert newConcert) {
return oldConcert.getTitle().equals(newConcert.getTitle());
}
@Override
public boolean areContentsTheSame(Concert oldConcert,
Concert newConcert) {
return oldConcert.equals(newConcert);
}
};
}
??在使用PagedListAdapter時(shí),我們需要注意的是,PagedListAdapter內(nèi)部默認(rèn)實(shí)現(xiàn)DiffUtil來進(jìn)行數(shù)據(jù)的差量計(jì)算,所以我們?cè)跇?gòu)造方法里面?zhèn)鬟f一個(gè)DiffUtil.ItemCallback。至于DiffUtil是什么,以及它內(nèi)部的算法實(shí)現(xiàn),我們這里就不介紹,有興趣的同學(xué)可以關(guān)注樓主的RecyclerView源碼分析系列文章,在不久的將來,我會(huì)詳細(xì)的分析它的內(nèi)部實(shí)現(xiàn)。
??然后,我們?cè)賮砜纯?code>Activity里面是怎么使用的:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private RecyclerAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.recyclerView);
adapter = new RecyclerAdapter();
ConcertViewModel viewModel =
ViewModelProviders.of(this).get(ConcertViewModel.class);
viewModel.getConvertList().observe(this, concerts -> adapter.submitList(concerts));
mRecyclerView.setAdapter(adapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
mRecyclerView.addItemDecoration(new SimpleDividerDecoration(this));
}
}
??整個(gè)結(jié)構(gòu)就這樣的,是不是非常簡(jiǎn)單?不過,到這里可能有人可能有疑問,我就實(shí)現(xiàn)了一個(gè)RecyclerView加載更多的數(shù)據(jù),你就讓我定義這么多的類,是不是有點(diǎn)小題大做呢?
??其實(shí)不然,Jetpack是Google官方推薦的一種App架構(gòu)方案,在這種結(jié)構(gòu)下,會(huì)避免很多的問題,比如說內(nèi)存泄漏和空指針異常等等。之所以使用這種方案來架構(gòu)App,不是我們吃飽撐的沒事干,而是為了解決傳統(tǒng)架構(gòu)方案下不能解決的問題。當(dāng)然可能還有其他的第三方解決方案,但是肯定不比Jetpack權(quán)威,畢竟Google爸爸親自設(shè)計(jì)的。
5. 源碼
??為了讓大家更好理解paging的使用,我將我的代碼上傳到github,供大家參考,鏈接:PagingDemo。