乐趣区

关于android:Paging-30-简介-MAD-Skills

欢送浏览 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 所定义的范畴获取和刷新数据。

PagingSourceMap 相似,都须要定义两个泛型类型: 分页的 Key 的类型和加载的数据的类型。举例来说,从基于 Github API 的页面获取 Repo 我的项目的 PagingSource 的申明,能够定义为:

/* Copyright 2020 Google LLC.  
   SPDX-License-Identifier: Apache-2.0 */

class GithubPagingSource(…) : PagingSource<Int, Repo>()

△ PagingSource 申明

性能残缺的 PagingSource 须要实现两个形象办法:

  1. load()
  2. 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 以及填充咱们的仓库列表,敬请关注咱们后续的文章。

欢迎您 点击这里 向咱们提交反馈,或分享您喜爱的内容、发现的问题。您的反馈对咱们十分重要,感谢您的反对!

退出移动版