【发布时间】:2022-07-13 17:21:34
【问题描述】:
问题
我有本地生成的数据,需要在 RecyclerView 中显示。我尝试使用自定义PagingSource 和PagingDataAdapter 来减少内存中的数据量,但是当我使数据无效时会出现视觉效果,例如,如果我插入或删除一项:
- 当可见项属于 2 个不同的“页面”时,许多未更改的项会像被修改过一样闪烁
- 如果所有可见项都属于同一页面,则一切正常。只有插入/删除的项目会显示动画。
我使用 Google 文档 (PagingSample) 引用的示例应用程序来测试这个概念。带有 Room 的原始版本不显示人工制品,但带有自定义 PagingSource 的修改版本可以。
Room 生成和使用的代码过于复杂,看不出任何可以解释问题的差异。
我的数据必须是本地生成的,我不能使用 Room 作为解决方法来显示它们。
我的问题
如何为我的本地数据正确定义 PagingSource,并将其与 PagingDataAdapter 一起使用而不会出现视觉故障?
(可选)我如何知道数据何时被丢弃(这样我也可以丢弃本地数据)?
代码摘录和详细信息
完整的示例项目托管在这里:https://github.com/blueglyph/PagingSampleModified
这是数据:
private val _data = ArrayMap<Int, Cheese>()
val data = MutableLiveData <Map<Int, Cheese>>(_data)
val sortedData = data.map { data -> data.values.sortedBy { it.name.lowercase() } }
和PagingSource。我正在使用 key = item 位置。我试过用 key = 页码,每页包含 30 个项目(10 个可见),但它并没有改变任何东西。
private class CheeseDataSource(val dao: CheeseDaoLocal, val pageSize: Int): PagingSource<Int, Cheese>() {
fun max(a: Int, b: Int): Int = if (a > b) a else b
override fun getRefreshKey(state: PagingState<Int, Cheese>): Int? {
val lastPos = dao.count() - 1
val key = state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(pageSize)?.coerceAtMost(lastPos) ?: anchorPage?.nextKey?.minus(pageSize)?.coerceAtLeast(0)
}
return key
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Cheese> {
val pageNumber = params.key ?: 0
val count = dao.count()
val data = dao.allCheesesOrdName().drop(pageNumber).take(pageSize)
return LoadResult.Page(
data = data,
prevKey = if (pageNumber > 0) max(0, pageNumber - pageSize) else null,
nextKey = if (pageNumber + pageSize < count) pageNumber + pageSize else null
)
}
}
PagingData 上的Flow 是在视图模型中创建的:
val pageSize = 30
var dataSource: PagingSource<Int, Cheese>? = null
val allCheeses: Flow<PagingData<CheeseListItem>> = Pager(
config = PagingConfig(
pageSize = pageSize,
enablePlaceholders = false,
maxSize = 90
)
) {
dataSource = dao.getDataSource(pageSize)
dataSource!!
}.flow
.map { pagingData -> pagingData.map { cheese -> CheeseListItem.Item(cheese) } }
dao.getDataSource(pageSize) 返回上面显示的CheeseDataSource。
在活动中,数据页面被收集并提交:
lifecycleScope.launch {
viewModel.allCheeses.collectLatest { adapter.submitData(it) }
}
当数据被修改时,观察者触发失效:
dao.sortedData.observeForever {
dataSource?.invalidate()
}
页面的滚动和加载都很好,唯一的问题是使用invalidate和同时显示2个页面的项目时。
适配器很经典:
class CheeseAdapter : PagingDataAdapter<CheeseListItem, CheeseViewHolder>(diffCallback) {
...
companion object {
val diffCallback = object : DiffUtil.ItemCallback<CheeseListItem>() {
override fun areItemsTheSame(oldItem: CheeseListItem, newItem: CheeseListItem): Boolean {
return if (oldItem is CheeseListItem.Item && newItem is CheeseListItem.Item) {
oldItem.cheese.id == newItem.cheese.id
} else if (oldItem is CheeseListItem.Separator && newItem is CheeseListItem.Separator) {
oldItem.name == newItem.name
} else {
oldItem == newItem
}
}
override fun areContentsTheSame(oldItem: CheeseListItem, newItem: CheeseListItem): Boolean {
return oldItem == newItem
}
}
...
我尝试过的事情(以及许多其他事情)
- 使用 LiveData 代替 Flow
- 使用/删除 Flow 上的缓存
- 直接在插入/删除函数中移除观察者和无效化,让代码更直接
- 使用 key = 页码 (0, 1, 2, ...) 代替 key = position,每个页面包含 pageSize=30 个项目
此时,我不确定 paging-3 是否用于自定义数据。我正在观察一个简单的插入/删除操作,例如适配器中的 2000-4000 比较操作,重新加载 3 页数据,......直接在我的数据上使用 ListAdapter 并手动执行加载/卸载似乎是更好的选择。
【问题讨论】:
标签: android kotlin android-paging-3