共计 4166 个字符,预计需要花费 11 分钟才能阅读完成。
欢送浏览 MAD Skills 系列 之 Paging 3.0!在本文中,我将介绍 Paging 3.0 并重点阐明如何将其集成至您利用的数据层。如果您更喜爱通过视频理解此内容,请 点击此处 查看。
为什么应用 Paging 3.0?
向用户展现一列数据是最常见的 UI 模式之一。当您须要加载大量数据时,能够通过分块异步获取 / 显示数据来晋升利用性能。这一模式是如此常见,如果有依赖库能够提供促成实现该模式的形象,将会为开发者带来微小的便当。这便是 Paging 3.0 致力解决的用例。作为额定的益处,它还让您的利用能够反对有限的数据汇合;而如果您的利用通过网络加载数据,它也为反对本地缓存提供了不便。
如果您正在应用 Paging 2.0,那么 Paging 3.0 也为其后任所蕴含的性能提供了一系列改良:
- 优先反对 Kotlin 协程和 Flow。
- 反对通过 RxJava Single 或 Guava ListenableFuture 原语进行异步加载。
- 为响应式 UI 设计提供了内建的加载状态和谬误信号,包含重试和刷新性能。
- 改良仓库层,蕴含对于可勾销的反对及简化数据源接口。
- 改良体现层、列表分隔符、自定义页面转换以及加载状态头、脚标。
如需获取更多内容信息,请查阅 Paging 2.0 到 Paging 3.0 的 迁徙文档。
置入数据
在您利用的架构计划中,Paging 3.0 最适宜作为从数据层获取数据并通过 ViewModel
在 UI 层传输数据来对其进行转换和出现的一种形式。在 Paging 3.0 中,咱们通过名为 PagingSource
的类型拜访您的数据层,该类型定义了如何围绕 PagingConfig
所定义的范畴获取和刷新数据。
PagingSource
和 Map
相似,都须要定义两个泛型类型: 分页的 Key 的类型和加载的数据的类型。举例来说,从基于 Github API 的页面获取 Repo
我的项目的 PagingSource
的申明,能够定义为:
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
class GithubPagingSource(…) : PagingSource<Int, Repo>()
△ PagingSource 申明
性能残缺的 PagingSource 须要实现两个形象办法:
load()
getRefreshKey()
load 办法
load()
办法正如其名,是由 Paging 库所调用的,用于异步加载要显示的数据的办法。这一办法会在初始加载或者响应用户滑动至边界时调用。load 办法会传入一个 LoadParams 对象,您能够通过它来确定如何触发 load 办法的调用。此对象中蕴含了无关 load 操作的信息,包含:
- 将要加载的页面的 Key: 如果这是 load 办法第一次被调用 (初始加载),
LoadParams.key
将会是null
。在这种状况下,您必须定义初始页面 Key。 - 加载大小: 申请所要加载的我的项目的数量。
load 办法的返回类型是 LoadResult
。它能够是:
LoadResult.Page
: 针对加载胜利。LoadResult.Error
: 针对加载失败。
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Repo> {
val position = params.key ?: GITHUB_STARTING_PAGE_INDEX
val apiQuery = query + IN_QUALIFIER
return try {val response = service.searchRepos(apiQuery, position, params.loadSize)
val repos = response.items
val nextKey = if (repos.isEmpty()) {null} else {
// 初始加载大小为 3 * NETWORK_PAGE_SIZE
// 要保障咱们在第二次加载时不会去申请反复的我的项目。position + (params.loadSize / NETWORK_PAGE_SIZE)
}
LoadResult.Page(
data = repos,
prevKey = if (position == GITHUB_STARTING_PAGE_INDEX) null else position - 1,
nextKey = nextKey
)
} catch (exception: IOException) {LoadResult.Error(exception)
} catch (exception: HttpException) {LoadResult.Error(exception)
}
}
△ load 办法实现
留神,默认状况下,初始加载大小为分页大小的三倍。这样能够保障在列表第一次加载时,即便用户稍作滚动,也能看到足够的数据,从而防止触发太多网络申请。这也是在 PagingSource
实现中计算下一个 Key 时所须要思考的事件。
getRefreshKey 办法
刷新 Key 用于 PagingSource.load()
办法后续的刷新调用 (第一次调用是初始加载,应用为 Pager 提供的初始 Key)。每当 Paging 库想要加载新的数据来代替以后列表 (例如,下拉刷新或数据库更新、配置变更、过程终止等状况的产生而导致数据生效) 时,便会产生刷新操作。通常,后续刷新调用会想要从新加载以 PagingState.anchorPosition
为核心的数据,而 PagingState.anchorPosition
则代表了最近所拜访的索引地位。
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
// 刷新 Key 用于在初始加载的数据生效后下一个 PagingSource 的加载。override fun getRefreshKey(state: PagingState<Int, Repo>): Int? {
// 咱们须要获取与最新拜访索引最靠近页面的前一个 Key(如果上一个 Key 为空,则为下一个 Key)// anchorPosition 即为最近拜访的索引
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
△ getRefreshKey 办法实现
Pager 对象
在定义了 PagingSource
后,咱们当初能够创立 Pager
了。Pager
类负责依据 UI 的申请从 PagingSource
中增量拉取数据汇合。因为 Pager
须要拜访 PagingSource
,所以它通常创立在定义 PagingSource
的数据层中。
结构 Pager
所需的另一个类是 PagingConfig
,它定义了管制 Pager
获取数据形式的参数。除了必选的 pageSize
参数外,PagingConfig
还裸露了许多可选参数,您能够通过它们微调 Pager
的行为:
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
private const val NETWORK_PAGE_SIZE = 30
class GithubRepository(private val service: GithubService) {fun getSearchResultStream(query: String): Flow<PagingData<Repo>> {Log.d("GithubRepository", "New query: $query")
return Pager(
config = PagingConfig(
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false
),
pagingSourceFactory = {GithubPagingSource(service, query) }
).flow
}
}
△ 创立 Pager
下面结构 PagingConfig
的代码中所应用参数的简要阐明如下:
pageSize
: 每次要从PagingSource
加载我的项目的数量。enablePlaceholders
: 是否须要PagingData
为尚未加载的数据返回 null。
通常咱们会心愿 pageSize
足够的大 (至多足够填充界面的可视区域,但最好是这一数量的 2 到 3 倍),这样 Pager
就不用为了在屏幕上显示足够的内容,而在用户进行滚动操作时一遍又一遍地获取数据了。
获取您的数据
Pager
所产生的类型是 PagingData
,该类型提供了进入其背地 PagingSource
的不同窗口。当用户滚动列表时,PagingData
会继续从 PagingSource
中获取数据以提供内容。如果 PagingSource
生效,Pager
会收回一个新的 PagingData
以确保曾经分页的我的项目与 UI 中显示的内容同步。将 PagingData
视为某个工夫节点中 PagingSource
的快照可能会对您的了解有所帮忙。
因为 PagingSource
是在 PagingSource
生效时产生扭转的快照,因而 Paging 库提供了多种以流的模式应用 PagingData
的形式:
- Kotlin Flow 通过
Pager.flow
- LiveData 通过
Pager.liveData
- RxJava Flowable 通过
Pager.flowable
- RxJava Observable 通过
Pager.observable
PagingData
的流能够在展现分页我的项目到 UI 前通过 ViewModel
进行操作和转换。
后续
依照如上步骤,咱们曾经将 Paging 3.0 集成到了您利用的数据层中!如何在 UI 中生产 PagingData 以及填充咱们的仓库列表,敬请关注咱们后续的文章。
欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!