Paging分页加载 #
一、Paging概述 #
Paging是Jetpack提供的分页加载组件,用于高效加载和显示大量数据。
1.1 Paging的优势 #
- 内存高效
- 自动加载更多
- 支持多种数据源
- 支持列表更新
1.2 添加依赖 #
kotlin
dependencies {
implementation("androidx.paging:paging-runtime-ktx:3.2.1")
}
二、PagingSource #
2.1 创建PagingSource #
kotlin
class UserPagingSource(
private val apiService: ApiService
) : PagingSource<Int, User>() {
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, User> {
return try {
val page = params.key ?: 1
val response = apiService.getUsers(page, params.loadSize)
LoadResult.Page(
data = response.users,
prevKey = if (page == 1) null else page - 1,
nextKey = if (response.users.isEmpty()) null else page + 1
)
} catch (e: Exception) {
LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, User>): Int? {
return state.anchorPosition?.let { anchorPosition ->
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
}
}
}
2.2 Room PagingSource #
kotlin
@Dao
interface UserDao {
@Query("SELECT * FROM users ORDER BY name ASC")
fun getPagingSource(): PagingSource<Int, User>
}
三、Pager #
3.1 创建Pager #
kotlin
class UserRepository(private val apiService: ApiService) {
fun getUsers(): Flow<PagingData<User>> {
return Pager(
config = PagingConfig(
pageSize = 20,
prefetchDistance = 10,
enablePlaceholders = false
),
pagingSourceFactory = { UserPagingSource(apiService) }
).flow
}
}
3.2 PagingConfig参数 #
| 参数 | 说明 |
|---|---|
| pageSize | 每页数据量 |
| prefetchDistance | 预加载距离 |
| enablePlaceholders | 是否启用占位符 |
| initialLoadSize | 初始加载数量 |
| maxSize | 最大缓存数量 |
四、PagingDataAdapter #
4.1 创建Adapter #
kotlin
class UserAdapter(
private val onClick: (User) -> Unit
) : PagingDataAdapter<User, UserViewHolder>(DiffCallback) {
companion object DiffCallback : DiffUtil.ItemCallback<User>() {
override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
return oldItem == newItem
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
val binding = ItemUserBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return UserViewHolder(binding)
}
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val user = getItem(position)
user?.let { holder.bind(it, onClick) }
}
}
class UserViewHolder(
private val binding: ItemUserBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(user: User, onClick: (User) -> Unit) {
binding.name.text = user.name
binding.root.setOnClickListener { onClick(user) }
}
}
4.2 在Activity中使用 #
kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
private lateinit var adapter: UserAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
adapter = UserAdapter { user ->
// 点击事件
}
binding.recyclerView.adapter = adapter
lifecycleScope.launch {
viewModel.users.collectLatest { pagingData ->
adapter.submitData(pagingData)
}
}
}
}
五、ViewModel配置 #
kotlin
@HiltViewModel
class MainViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
val users: Flow<PagingData<User>> = repository.getUsers()
.cachedIn(viewModelScope)
}
六、加载状态 #
6.1 添加加载状态Footer #
kotlin
class LoadStateAdapter(
private val retry: () -> Unit
) : RecyclerView.Adapter<LoadStateViewHolder>() {
private var loadState: LoadState = LoadState.NotLoading(false)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LoadStateViewHolder {
val binding = ItemLoadStateBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return LoadStateViewHolder(binding, retry)
}
override fun onBindViewHolder(holder: LoadStateViewHolder, position: Int) {
holder.bind(loadState)
}
override fun getItemCount(): Int = 1
fun setLoadState(state: LoadState) {
loadState = state
notifyDataSetChanged()
}
}
class LoadStateViewHolder(
private val binding: ItemLoadStateBinding,
private val retry: () -> Unit
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.retryButton.setOnClickListener { retry() }
}
fun bind(loadState: LoadState) {
binding.progressBar.isVisible = loadState is LoadState.Loading
binding.retryButton.isVisible = loadState is LoadState.Error
binding.errorMsg.isVisible = loadState is LoadState.Error
if (loadState is LoadState.Error) {
binding.errorMsg.text = loadState.error.localizedMessage
}
}
}
6.2 使用withLoadStateFooter #
kotlin
binding.recyclerView.adapter = adapter.withLoadStateFooter(
footer = LoadStateAdapter { adapter.retry() }
)
6.3 监听加载状态 #
kotlin
adapter.addLoadStateListener { loadState ->
binding.progressBar.isVisible = loadState.refresh is LoadState.Loading
binding.retryButton.isVisible = loadState.refresh is LoadState.Error
val errorState = loadState.source.append as? LoadState.Error
?: loadState.source.prepend as? LoadState.Error
?: loadState.append as? LoadState.Error
?: loadState.prepend as? LoadState.Error
errorState?.let {
Toast.makeText(this, it.error.message, Toast.LENGTH_LONG).show()
}
}
七、数据刷新 #
7.1 刷新数据 #
kotlin
// 刷新数据
adapter.refresh()
// 重试失败
adapter.retry()
7.2 过滤和搜索 #
kotlin
class MainViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> = _searchQuery
val users: Flow<PagingData<User>> = searchQuery
.debounce(300)
.flatMapLatest { query ->
repository.searchUsers(query)
}
.cachedIn(viewModelScope)
fun setSearchQuery(query: String) {
_searchQuery.value = query
}
}
八、RemoteMediator #
8.1 创建RemoteMediator #
kotlin
@OptIn(ExperimentalPagingApi::class)
class UserRemoteMediator(
private val apiService: ApiService,
private val database: AppDatabase
) : RemoteMediator<Int, User>() {
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, User>
): MediatorResult {
return try {
val page = when (loadType) {
LoadType.REFRESH -> 1
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> {
val lastItem = state.lastItemOrNull()
if (lastItem == null) {
1
} else {
(lastItem.id / state.config.pageSize) + 1
}
}
}
val response = apiService.getUsers(page)
database.withTransaction {
if (loadType == LoadType.REFRESH) {
database.userDao().clearAll()
}
database.userDao().insertAll(response.users)
}
MediatorResult.Success(
endOfPaginationReached = response.users.isEmpty()
)
} catch (e: IOException) {
MediatorResult.Error(e)
} catch (e: HttpException) {
MediatorResult.Error(e)
}
}
}
8.2 使用RemoteMediator #
kotlin
@OptIn(ExperimentalPagingApi::class)
fun getUsers(): Flow<PagingData<User>> {
return Pager(
config = PagingConfig(pageSize = 20),
remoteMediator = UserRemoteMediator(apiService, database),
pagingSourceFactory = { database.userDao().getPagingSource() }
).flow
}
九、总结 #
本章详细介绍了Paging分页加载:
- Paging的基本概念
- PagingSource的创建
- Pager的配置
- PagingDataAdapter的使用
- 加载状态处理
- 数据刷新
- RemoteMediator的使用
Paging是处理大数据集分页加载的最佳方案,可以高效地加载和显示大量数据。
最后更新:2026-03-26