本指南建立在 分页库概述 的基础上,讨论了如何自定义应用的数据加载解决方案以满足应用的架构需求。
构建可观察的列表
通常,您的 UI 代码会观察一个 LiveData<PagedList>
对象(或者,如果您使用的是 RxJava2,则为 Flowable<PagedList>
或 Observable<PagedList>
对象),该对象位于应用的 ViewModel
中。这个可观察的对象在应用列表数据的呈现和内容之间建立了连接。
为了创建这些可观察的 PagedList
对象之一,请将 DataSource.Factory
的实例传递给 LivePagedListBuilder
或 RxPagedListBuilder
对象。一个 DataSource
对象为单个 PagedList
加载页面。工厂类会创建新的 PagedList
实例,以响应内容更新(例如,数据库表失效和网络刷新)。Room 持久性库 可以为您提供 DataSource.Factory
对象,或者您可以 构建自己的。
以下代码片段展示了如何在应用的 ViewModel
类中使用 Room 的 DataSource.Factory
构建功能创建新的 LiveData<PagedList>
实例
Kotlin
@Dao interface ConcertDao { // The Int type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> }
Java
@Dao public interface ConcertDao { // The Integer type parameter tells Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") DataSource.Factory<Integer, Concert> concertsByDate(); }
Kotlin
// The Int type argument corresponds to a PositionalDataSource object. val myConcertDataSource : DataSource.Factory<Int, Concert> = concertDao.concertsByDate() val concertList = myConcertDataSource.toLiveData(pageSize = 50)
Java
// The Integer type argument corresponds to a PositionalDataSource object. DataSource.Factory<Integer, Concert> myConcertDataSource = concertDao.concertsByDate(); LiveData<PagedList<Concert>> concertList = LivePagedListBuilder(myConcertDataSource, /* page size */ 50).build();
定义自己的分页配置
为了在更高级的情况下进一步配置 LiveData<PagedList>
,您还可以定义自己的分页配置。特别是,您可以定义以下属性
- 页面大小: 每个页面中的项目数。
- 预取距离: 给定应用程序 UI 中的最后一个可见项目,分页库应尝试提前获取的超出此最后一个项目的项目数。此值应是页面大小的几倍。
- 占位符存在: 确定 UI 是否显示尚未完成加载的列表项目的占位符。有关使用占位符的优缺点的讨论,请了解如何 在您的 UI 中提供占位符。
如果您希望更多地控制分页库何时从您的应用程序数据库加载列表,请将自定义 Executor
对象传递给 LivePagedListBuilder
,如以下代码片段所示
Kotlin
val myPagingConfig = Config( pageSize = 50, prefetchDistance = 150, enablePlaceholders = true ) // The Int type argument corresponds to a PositionalDataSource object. val myConcertDataSource : DataSource.Factory<Int, Concert> = concertDao.concertsByDate() val concertList = myConcertDataSource.toLiveData( pagingConfig = myPagingConfig, fetchExecutor = myExecutor )
Java
PagedList.Config myPagingConfig = new PagedList.Config.Builder() .setPageSize(50) .setPrefetchDistance(150) .setEnablePlaceholders(true) .build(); // The Integer type argument corresponds to a PositionalDataSource object. DataSource.Factory<Integer, Concert> myConcertDataSource = concertDao.concertsByDate(); LiveData<PagedList<Concert>> concertList = new LivePagedListBuilder<>(myConcertDataSource, myPagingConfig) .setFetchExecutor(myExecutor) .build();
选择正确的数据源类型
连接到最适合处理源数据结构的数据源非常重要
- 如果加载的页面嵌入了下一个/上一个键,请使用
PageKeyedDataSource
。例如,如果您从网络获取社交媒体帖子,则可能需要将nextPage
令牌从一次加载传递到后续加载。 - 如果您需要使用项目 N 中的数据来获取项目 N+1,请使用
ItemKeyedDataSource
。例如,如果您正在为讨论应用程序获取线程化评论,则可能需要传递最后一个评论的 ID 来获取下一个评论的内容。 - 如果您需要从数据存储中您选择的任何位置获取数据页面,请使用
PositionalDataSource
。此类支持请求从您选择的任何位置开始的一组数据项。例如,请求可能会返回从位置 1500 开始的 50 个数据项。
通知数据无效时
使用分页库时,由数据层通知应用程序的其他层表格或行何时已过期。为此,从您为应用程序选择的 DataSource
类中调用 invalidate()
。
构建您自己的数据源
如果您使用自定义本地数据解决方案,或者直接从网络加载数据,您可以实现 DataSource
子类之一。以下代码片段显示了一个根据给定音乐会开始时间进行键控的数据源
Kotlin
class ConcertTimeDataSource() : ItemKeyedDataSource<Date, Concert>() { override fun getKey(item: Concert) = item.startTime override fun loadInitial( params: LoadInitialParams<Date>, callback: LoadInitialCallback<Concert>) { val items = fetchItems(params.requestedInitialKey, params.requestedLoadSize) callback.onResult(items) } override fun loadAfter( params: LoadParams<Date>, callback: LoadCallback<Concert>) { val items = fetchItemsAfter( date = params.key, limit = params.requestedLoadSize) callback.onResult(items) } }
Java
public class ConcertTimeDataSource extends ItemKeyedDataSource<Date, Concert> { @NonNull @Override public Date getKey(@NonNull Concert item) { return item.getStartTime(); } @Override public void loadInitial(@NonNull LoadInitialParams<Date> params, @NonNull LoadInitialCallback<Concert> callback) { List<Concert> items = fetchItems(params.key, params.requestedLoadSize); callback.onResult(items); } @Override public void loadAfter(@NonNull LoadParams<Date> params, @NonNull LoadCallback<Concert> callback) { List<Concert> items = fetchItemsAfter(params.key, params.requestedLoadSize); callback.onResult(items); }
然后,您可以通过创建 DataSource.Factory
的具体子类,将此自定义数据加载到 PagedList
对象中。以下代码片段显示了如何生成前一个代码片段中定义的自定义数据源的新实例
Kotlin
class ConcertTimeDataSourceFactory : DataSource.Factory<Date, Concert>() { val sourceLiveData = MutableLiveData<ConcertTimeDataSource>() var latestSource: ConcertDataSource? override fun create(): DataSource<Date, Concert> { latestSource = ConcertTimeDataSource() sourceLiveData.postValue(latestSource) return latestSource } }
Java
public class ConcertTimeDataSourceFactory extends DataSource.Factory<Date, Concert> { private MutableLiveData<ConcertTimeDataSource> sourceLiveData = new MutableLiveData<>(); private ConcertDataSource latestSource; @Override public DataSource<Date, Concert> create() { latestSource = new ConcertTimeDataSource(); sourceLiveData.postValue(latestSource); return latestSource; } }
考虑内容更新的工作方式
在构建可观察的 PagedList
对象时,请考虑内容更新的工作方式。如果您直接从 Room 数据库 加载数据,则更新会自动推送到应用程序的 UI。
使用分页网络 API 时,通常需要用户交互,例如“滑动刷新”,作为使您最近使用的 DataSource
失效的信号。然后,您请求该数据源的新实例。以下代码片段演示了此行为
Kotlin
class ConcertActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { // ... concertTimeViewModel.refreshState.observe(this, Observer { // Shows one possible way of triggering a refresh operation. swipeRefreshLayout.isRefreshing = it == MyNetworkState.LOADING }) swipeRefreshLayout.setOnRefreshListener { concertTimeViewModel.invalidateDataSource() } } } class ConcertTimeViewModel(firstConcertStartTime: Date) : ViewModel() { val dataSourceFactory = ConcertTimeDataSourceFactory(firstConcertStartTime) val concertList: LiveData<PagedList<Concert>> = dataSourceFactory.toLiveData( pageSize = 50, fetchExecutor = myExecutor ) fun invalidateDataSource() = dataSourceFactory.sourceLiveData.value?.invalidate() }
Java
public class ConcertActivity extends AppCompatActivity { @Override public void onCreate(@Nullable Bundle savedInstanceState) { // ... viewModel.getRefreshState() .observe(this, new Observer<NetworkState>() { // Shows one possible way of triggering a refresh operation. @Override public void onChanged(@Nullable MyNetworkState networkState) { swipeRefreshLayout.isRefreshing = networkState == MyNetworkState.LOADING; } }; swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshListener() { @Override public void onRefresh() { viewModel.invalidateDataSource(); } }); } } public class ConcertTimeViewModel extends ViewModel { private LiveData<PagedList<Concert>> concertList; private DataSource<Date, Concert> mostRecentDataSource; public ConcertTimeViewModel(Date firstConcertStartTime) { ConcertTimeDataSourceFactory dataSourceFactory = new ConcertTimeDataSourceFactory(firstConcertStartTime); mostRecentDataSource = dataSourceFactory.create(); concertList = new LivePagedListBuilder<>(dataSourceFactory, 50) .setFetchExecutor(myExecutor) .build(); } public void invalidateDataSource() { mostRecentDataSource.invalidate(); } }
提供数据映射
分页库支持对由 DataSource
加载的项目的基于项目和基于页面的转换。
在以下代码片段中,音乐会名称和音乐会日期的组合映射到包含名称和日期的单个字符串
Kotlin
class ConcertViewModel : ViewModel() { val concertDescriptions : LiveData<PagedList<String>> init { val concerts = database.allConcertsFactory() .map { "${it.name} - ${it.date}" } .toLiveData(pageSize = 50) } }
Java
public class ConcertViewModel extends ViewModel { private LiveData<PagedList<String>> concertDescriptions; public ConcertViewModel(MyDatabase database) { DataSource.Factory<Integer, Concert> factory = database.allConcertsFactory().map(concert -> concert.getName() + "-" + concert.getDate()); concertDescriptions = new LivePagedListBuilder<>( factory, /* page size */ 50).build(); } }
如果要在加载项目后包装、转换或准备项目,这将很有用。由于这项工作是在提取执行程序上完成的,因此您可以执行可能昂贵的操作,例如从磁盘读取或查询单独的数据库。
提供反馈
通过以下资源与我们分享您的反馈和想法
- 问题跟踪器
- 报告问题,以便我们修复错误。
其他资源
要了解有关分页库的更多信息,请参阅以下资源。
示例
Codelabs
视频
推荐给您
- 注意:当 JavaScript 关闭时显示链接文本
- 迁移到 Paging 3
- 分页 2 库概述
- 显示分页列表