一、Paging 简介
Paging 是 Jetpack 组件库的一个组件,是 Google 官方提供的分页加载数据框架,主要用于数据的分页加载。
Paging 主要由三个部分组成:
- DataSource
- PagedList
- PagedListAdapter
二、Paging 核心构成
1. DataSource
DataSource<Key, Value>是一个数据源,其中Key对应加载数据的条件信息,Value对应加载数据的实体类。
DataSource是一个抽象类,但是我们不能直接继承它实现它的子类。但是Paging库里提供了它的三个子类供我们继承用于不同场景的实现:
- PageKeyedDataSource<Key, Value>:适用于目标数据根据页信息请求数据的场景,即Key字段是页相关的信息。比如请求的数据的参数中包含类似next / pervious页数的信息。
- ItemKeyedDataSource<Key, Value>:适用于目标数据的加载依赖特定item的信息, 即Key字段包含的是Item中的信息,比如需要根据第N项的信息加载第N+1项的数据,传参中需要传入第N项的ID时,该场景多出现于论坛类应用评论信息的请求。
- PositionalDataSource<T>:适用于目标数据总数固定,通过特定的位置加载数据,这里Key是Integer类型的位置信息,T即Value。 比如从数据库中的1200条开始加在20条数据。
2. PageList
PageList是一个List的子类,支持所有List的操作,除此之外它主要有五个成员:
(1)MainThreadExecutor:一个主线程的Excutor, 用于将结果post到主线程。
(2)BackgroundThreadExecutor:后台线程的Excutor。
(3)BoundaryCallback:加载Datasource中的数据加载到边界时的回调。
(4)Config:配置PagedList从Datasource加载数据的方式, 其中包含以下属性:
pageSize
:设置每页加载的数量prefetchDistance
:预加载的数量,默认为pagesizeinitialLoadSizeHint
:初始化数据时加载的数量,默认为pagesize*3enablePlaceholders
:当item为null是否使用PlaceHolder展示
(5)PagedStorage<T>:用于存储加载到的数据,它是真正的蓄水池所在,它包含一个ArrayList<List> 对象mPages,按页存储数据。
PagedList会从Datasource中加载数据,更准确的说是通过Datasource加载数据, 通过Config的配置,可以设置一次加载的数量以及预加载的数量。 除此之外,PagedList还可以向RecyclerView.Adapter发送更新的信号,驱动UI的刷新。
3. PagedListAdapte
PagedListAdapte是RecyclerView.Adapter的实现,用于展示PagedList的数据。它本身实现的更多是Adapter的功能,但是它有一个小伙伴PagedListAdapterHelper<T>,PagedListAdapterHelper会负责监听PagedList的更新,Item数量的统计等功能。这样当PagedList中新一页的数据加载完成时,PagedAdapte就会发出加载完成的信号,通知RecyclerView刷新,这样就省略了每次Loading后手动调一次notifyDataChanged()。
除此之外,当数据源变动产生新的PagedList,PagedAdapter会在后台线程中比较前后两个PagedList的差异,然后调用notifyItem…()方法更新RecyclerView.这一过程依赖它的另一个小伙伴ListAdapterConfig, ListAdapterConfig负责主线程和后台线程的调度以及DiffCallback的管理,DiffCallback的接口实现中定义比较的规则,比较的工作则是由PagedStorageDiffHelper来完成。
三者之间的关系以及数据加载到数据展示的流程如下:
当一条新的item插入到数据库,DataSource会被初始化,LiveData后台线程就会创建一个新的PagedList。这个新的PagedList会被发送到UI线程的PagedListAdapter中,PagedListAdapter使用DiffUtil在对比现在的Item和新建Item的差异。当对比结束,PagedListAdapter通过调用RecycleView.Adapter.notifyItemInserted()将新的item插入到适当的位置。
三、Paging 使用
1. 导入依赖库
implementation 'androidx.paging:paging-runtime:2.1.1'
2. 创建 DataRepository
创建 DataRepository 用于数据的加载,这一步不是必须的,但一般为了获取数据的逻辑更加清晰,通常会创建对应的 Repository 类。
public class DataRepository {
private List<Data> dataList = new ArrayList<>();
public DataRepository() {
for (int i = 0; i < 1000; i++) {
Data data = new Data(i, "name " + i);
dataList.add(data);
}
}
public List<Data> initData(int size) {
return dataList.subList(0, size);
}
public List<Data> loadPageData(int page, int size) {
int totalPage;
if (dataList.size() % size == 0) {
totalPage = dataList.size() / size;
} else {
totalPage = dataList.size() / size + 1;
}
if (page > totalPage || page < 1) {
return null;
}
if (page == totalPage) {
return dataList.subList((page - 1) * size, dataList.size());
}
return dataList.subList((page - 1) * size, page * size);
}
}
3. 自定义 DataSource
public class CustomPageDataSource extends PageKeyedDataSource<Integer, Data> {
private DataRepository dataRepository;
private CustomPageDataSource(DataRepository dataRepository) {
this.dataRepository = dataRepository;
}
@Override
public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Data> callback) {
List<Data> dataList = dataRepository.initData(params.requestedLoadSize);
callback.onResult(dataList, null, 2);
}
@Override
public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Data> callback) {
List<Data> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
if (dataList != null) {
callback.onResult(dataList, params.key - 1);
}
}
@Override
public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Data> callback) {
List<Data> dataList = dataRepository.loadPageData(params.key, params.requestedLoadSize);
if (dataList != null) {
callback.onResult(dataList, params.key + 1);
}
}
public static class CustomPageDataSourceFactory extends DataSource.Factory<Integer, Data> {
private DataRepository repository;
public CustomPageDataSourceFactory(DataRepository repository) {
this.repository = repository;
}
@NonNull
@Override
public DataSource<Integer, Data> create() {
return new CustomPageDataSource(repository);
}
}
}
CustomItemDataSource
继承自PageKeyedDataSource
,实现了loadInitial
、loadAfter
、loadBefore
方法,其中
loadInitial
初始加载数据loadAfter
向后分页加载数据loadBefore
向前分页加载数据
其中params包装了分页加载的参数。
loadInitial
中的params为LoadInitialParams
包含了requestedLoadSize
和placeholdersEnabled
两个属性:
- requestedLoadSize为加载的数据量
- placeholdersEnabled是是否显示占位及当数据为null时显示占位的view
loadBefore
和loadAfter
中的params为LoadParams
包含了key
和requestedLoadSize
,key即为DataSource<Key, Value>
中的key,在这里即为页数。
callback
为数据加载完成的回调,loadInitial
中调用调用DataRepository
加载数据,然后调用callback.onResult
告诉调用者数据加载完成。
4. 创建 PageAdapter
public class CustomAdapter extends PagedListAdapter<Data, CustomAdapter.CustomViewHolder> {
public CustomAdapter() {
super(new DiffUtil.ItemCallback<Data>() {
@Override
public boolean areItemsTheSame(@NonNull Data oldItem, @NonNull Data newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(@NonNull Data oldItem, @NonNull Data newItem) {
return oldItem.getName().equals(newItem.getName());
}
});
}
@NonNull
@Override
public CustomViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.cell, parent, false);
return new CustomViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull CustomViewHolder holder, int position) {
Data data = getItem(position);
if (data == null) {
holder.textView.setText("loading...");
} else {
holder.textView.setText(String.valueOf(data.getName()));
}
}
static class CustomViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public CustomViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
}
}
}
DiffUtil.ItemCallback
的作用是判断两个item的数据是否相等。
5. 创建PagedList,并使用
recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.VERTICAL, false));
customAdapter = new CustomAdapter();
recyclerView.setAdapter(customAdapter);
PagedList.Config config = new PagedList.Config.Builder()
.setPageSize(20)
.setEnablePlaceholders(true)
.setInitialLoadSizeHint(20)
.build();
dataRepository = new DataRepository();
customPageDataSourceFactory = new CustomPageDataSource.CustomPageDataSourceFactory(dataRepository);
dataList = new LivePagedListBuilder(customPageDataSourceFactory, config).build();
dataList.observe(this, new Observer<PagedList<Data>>() {
@Override
public void onChanged(PagedList<Data> dataPagedList) {
customAdapter.submitList(dataPagedList);
}
});
通过以上步骤,便实现了数据的分页加载。