说到Paging很多人应该都挺陌生的,但是它也是JetPack中的一员,既然Google能觉得用上他就如同坐上火箭,这里就来看一下到底怎么使用以及原理
google这里给出了两个实例
从字面上就可以看出来 一个是针对于网络的例子,话不多说 直接上码
在PagingSample中
// CheeseDao.kt
@Query("SELECT * FROM Cheese ORDER BY name COLLATE NOCASE ASC")
fun allCheesesByName(): PagingSource<Int, Cheese>
@Entity
data class Cheese(@PrimaryKey(autoGenerate = true) val id: Int, val name: String)
官方用了一个数据库数据读取,来作为paging的展示demo
数据格式 paging_database.PNG可以看到很简单,只有一个id和name
而那个PagingSource则在下面源码分析时在提,现在看上去它很像是一堆数据的查询事件结果
// CheeseViewModel.kt
val allCheeses = Pager(
PagingConfig(
//该值至少可以填充几个屏幕上的内容
pageSize = 60,
//简单来说,打开了,滚动条就是完整大小,关上了;随着多页面增加,滚动条会抖动(建议不显示滚动条)
enablePlaceholders = true,
//PagedList一次可以保存在内存中的最大项目数
maxSize = 200
)
) {
dao.allCheesesByName()
}.flow
以上部分就是从数据库中读取指定的数据
// MainActivity.kt
lifecycleScope.launch {
viewModel.allCheeses.collectLatest { adapter.submitData(it) }
}
页面则更简单了,获取的数据直接提交给页面,这个提交方式很类似ListAdapter
// CheeseAdapter.kt
class CheeseAdapter : PagingDataAdapter<Cheese, CheeseViewHolder>(diffCallback) {
override fun onBindViewHolder(holder: CheeseViewHolder, position: Int) {
holder.bindTo(getItem(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheeseViewHolder =
CheeseViewHolder(parent)
}
//CheeseViewHolder.kt
class CheeseViewHolder(parent :ViewGroup) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.cheese_item, parent, false)) {
private val nameView = itemView.findViewById<TextView>(R.id.name)
var cheese : Cheese? = null
fun bindTo(cheese : Cheese?) {
this.cheese = cheese
nameView.text = cheese?.name
}
}
我第一次看的时候,想着这就完了? 然后立马去修改请求的maxSize想看看会有什么反应
aximum size must be at least
pageSize + 2*prefetchDist, pageSize=20, prefetchDist=20, maxSize=20
然后就出现这个错误 ,我立马就明白了,最大的缓存需要是你展示单页数据的3倍 因为每次都要至少加载你上方,下方和当前页面 =。=
然后我把pageSize改成了15,maxSize改成了45,进入后迅速滑动,第一时间出现卡顿情况(预想是超出内存缓存,一次缓存45,之后就可以正常滑动了(在我看来应该是allCheesesByName把所有数据都落地了,这时候就要在submitData()这里断点看看数据是什么样子的))
然后我发现submitData()只被调用了一次,接着立刻查询了一下数据库里的数据
paging_database_num.PNGok有654条,再debug看submitData提交的数据
=。= 它居然是一个PagingData<T>的对象 好吧看来到这里就不得不分析源码了
PagingSource
首先来看PagingSource,它是一个抽象类
// PagingSource.kt
// PagedList.Config 调用 toRefreshLoadParams后 就会刷新参数
fun <Key : Any> PagedList.Config.toRefreshLoadParams(key: Key?): PagingSource.LoadParams<Key> =
PagingSource.LoadParams.Refresh(
key,
initialLoadSizeHint,
enablePlaceholders,
)
abstract class PagingSource<Key : Any, Value : Any> {
// 密封类 加载所需的参数
sealed class LoadParams<Key : Any> constructor(val loadSize: Int, val placeholdersEnabled: Boolean,){
abstract val key: Key?
// 字面意思就是刷新
class Refresh<Key : Any> constructor(
override val key: Key?,
loadSize: Int,
placeholdersEnabled: Boolean,
) : LoadParams<Key>(
loadSize = loadSize,
placeholdersEnabled = placeholdersEnabled,
)
// 向后加载
class Append<Key : Any> constructor(
override val key: Key,
loadSize: Int,
placeholdersEnabled: Boolean,
) : LoadParams<Key>(
loadSize = loadSize,
placeholdersEnabled = placeholdersEnabled,
)
// 向前加载
class Prepend<Key : Any> constructor(
override val key: Key,
loadSize: Int,
placeholdersEnabled: Boolean,
) : LoadParams<Key>(
loadSize = loadSize,
placeholdersEnabled = placeholdersEnabled,
)
// 伴生对象 这里可以看出来是对几种模式进行了赋值操作
internal companion object {
fun <Key : Any> create(
loadType: LoadType,
key: Key?,
loadSize: Int,
placeholdersEnabled: Boolean,
): LoadParams<Key> = when (loadType) {
LoadType.REFRESH -> Refresh( // 我是刷新
key = key,
loadSize = loadSize,
placeholdersEnabled = placeholdersEnabled,
)
LoadType.PREPEND -> Prepend( // 我是向后添加
loadSize = loadSize,
key = requireNotNull(key) {
"key cannot be null for prepend"
},
placeholdersEnabled = placeholdersEnabled,
)
LoadType.APPEND -> Append( // 我是向前添加
loadSize = loadSize,
key = requireNotNull(key) {
"key cannot be null for append"
},
placeholdersEnabled = placeholdersEnabled,
)
}
}
}
// 加载结果
sealed class LoadResult<Key : Any, Value : Any> {
// 错误类
data class Error<Key : Any, Value : Any>(
val throwable: Throwable
) : LoadResult<Key, Value>()
// Success result object for [PagingSource.load]
// 可以看出这是load返回成功的数据集
data class Page<Key : Any, Value : Any> constructor(
val data: List<Value>, // 数据
val prevKey: Key?, // 上一页 (可以为空)
val nextKey: Key?, // 下一页 (可以为空)
@IntRange(from = COUNT_UNDEFINED.toLong())
val itemsBefore: Int = COUNT_UNDEFINED,// 加载前项目数量
@IntRange(from = COUNT_UNDEFINED.toLong())
val itemsAfter: Int = COUNT_UNDEFINED// 加载后项目数量
) : LoadResult<Key, Value>() {
constructor(
data: List<Value>,
prevKey: Key?,
nextKey: Key?
) : this(data, prevKey, nextKey, COUNT_UNDEFINED, COUNT_UNDEFINED)
......
// 一小段初始化
}
open val jumpingSupported: Boolean // 是否支持跳转(是否是连续的)
get() = false
open val keyReuseSupported: Boolean // 是否支持重用Key
get() = false
}
以上就是PagingSource的代码了,它只是个抽象类,看上去目的是定义了大致的数据框架,确定了加载模式,并确定了数据格式,之后就到了CheeseViewModel中的flow操作了
// Pager.kt
class Pager<Key : Any, Value : Any>{
constructor(
config: PagingConfig,
initialKey: Key? = null,
remoteMediator: RemoteMediator<Key, Value>?,
pagingSourceFactory: () -> PagingSource<Key, Value>){
constructor(
config: PagingConfig,
initialKey: Key? = null,
pagingSourceFactory: () -> PagingSource<Key, Value>
) : this(config, initialKey, null, pagingSourceFactory)
}
val flow: Flow<PagingData<Value>> = PageFetcher(
pagingSourceFactory = if (
pagingSourceFactory is SuspendingPagingSourceFactory<Key, Value>
) {
pagingSourceFactory::create
} else {
{
pagingSourceFactory()
}
},
initialKey = initialKey,
config = config,
remoteMediator = remoteMediator
).flow
}
// 今天先到这,明天继续
网友评论