一、引入包
implementation(libs.paging.runtime)implementation(libs.paging.compose)
paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" }
paging-compose={module="androidx.paging:paging-compose", version.ref = "paging_version" }
版本:
paging_version = "3.3.6"
开发框架:
网络请求【Retrofit】
依赖注入【Koin】
主要使用场景:
1、和PullToRefreshBox一起使用,下拉刷新
2、上拉加载更多
3、对列表操作刷新当前页面
二、使用
1、接口
@POST("/tangPoetry/queryFavorite") suspend fun queryFavorite(@Body params: PageParams): BasePageResponse<TangPoetry>
2、Repository
package com.co.post.data.repositoryimport com.co.post.data.api.ApiService
import com.co.post.data.model.PageParams
import com.co.post.data.model.db.TangPoetry
import com.co.post.data.model.getDataIfSuccess
import com.co.post.data.model.success/*** @Author : Cook* @Date : 2025/3/11* @Desc :* @Version:*/
class PoetryRepository(private val apiService: ApiService) {suspend fun queryFavorite(pageNum: Int): Pair<Boolean, List<TangPoetry>> {val result = apiService.queryFavorite(PageParams(pageNum))val data = result.getDataIfSuccess() ?: emptyList()return Pair(result.data.isLastPage, data)}}
3、定义PagingSource
实现
getRefreshKey和load方法
getRefreshKey获取刷新的参数,可以自定义
load方法处理加载逻辑
nextKey参数为null时代表加载完成
package com.co.post.data.repository.pagingimport androidx.paging.PagingSource
import androidx.paging.PagingState
import com.co.post.data.model.db.TangPoetry
import com.co.post.data.repository.PoetryRepository/*** @Author : Cook* @Date : 2025/3/11* @Desc :* @Version:*/
class PoetryPagingSource(private val repository: PoetryRepository) :PagingSource<Int, TangPoetry>() {override fun getRefreshKey(state: PagingState<Int, TangPoetry>): Int? {return 1}override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TangPoetry> {return try {val page = params.key ?: 1val pair = repository.queryFavorite(page)LoadResult.Page(data = pair.second,prevKey = if (page == 1) null else page - 1,nextKey = if (pair.first) null else page + 1)} catch (e: Exception) {LoadResult.Error(e)}}}
4、ViewModel中调用
package com.co.post.ui.page.homeimport androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import com.co.post.base.BaseViewModel
import com.co.post.data.model.db.TangPoetry
import com.co.post.data.repository.PoetryRepository
import com.co.post.data.repository.paging.PoetryPagingSource
import com.co.post.utils.CacheHelper
import com.co.post.utils.Constants
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch/*** @Author : Cook* @Date : 2025/1/2* @Desc :* @Version:*/
class HomeViewModel(private val repository: PoetryRepository
) : BaseViewModel() {var viewStates by mutableStateOf(HomeViewState())private setprivate var pagingSource: PoetryPagingSource? = nullval tangPoetryPage = Pager(config = PagingConfig(pageSize = Constants.PAGE_SIZE,enablePlaceholders = false),pagingSourceFactory = {PoetryPagingSource(repository).also {pagingSource = it}}).flow.cachedIn(viewModelScope)fun dispatch(action: HomeViewAction) {when (action) {is HomeViewAction.Init -> init()is HomeViewAction.UnCollect -> unCollect(action.id)}}private fun unCollect(id: String) {viewModelScope.launch {flow {emit(repository.unCollectPoetry(id))}.catch {mViewEvents.send(CommonViewEvent.ErrorMessage(it.message ?: ""))}.collect { data ->val result = if (data) {pagingSource?.invalidate()"操作成功"} else {"操作失败"}mViewEvents.send(CommonViewEvent.SuccessMessage(result))}}}private fun init() {viewStates = viewStates.copy(isLogged = CacheHelper.isLogged())}}data class HomeViewState(val tangPoetryList: List<TangPoetry> = emptyList(),val isLogged: Boolean = CacheHelper.isLogged(),)sealed class HomeViewAction {object Init : HomeViewAction()data class UnCollect(val id: String) : HomeViewAction()
}
5、View中使用
package com.co.post.ui.page.homeimport androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import com.co.post.base.BaseViewModel.CommonViewEvent
import com.co.post.ui.page.common.Screen
import com.co.post.ui.theme.commonTopBarText
import com.co.post.ui.theme.themeColor
import com.co.post.ui.theme.white
import com.co.post.ui.theme.white10
import com.co.post.ui.widgets.CommonDevider
import com.co.post.ui.widgets.ErrorItem
import com.co.post.ui.widgets.LoadingItem
import com.co.post.ui.widgets.NoMoreItem
import com.co.post.ui.widgets.SNACK_ERROR
import com.co.post.ui.widgets.SNACK_SUCCESS
import com.co.post.ui.widgets.SelectOptionDialog
import com.co.post.ui.widgets.popupSnackBar
import com.co.post.utils.CacheHelper
import com.co.post.utils.PostAppUtils
import com.co.post.utils.PostAppUtils.getFirstPoetry
import com.co.post.utils.RouteUtils
import org.koin.androidx.compose.koinViewModel/*** @Author : Cook* @Date : 2024/9/29* @Desc :* @Version:*/@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(navController: NavHostController,snackBarHostState: SnackbarHostState,innerPadding: PaddingValues,viewModel: HomeViewModel = koinViewModel()
) {val dataPage = viewModel.tangPoetryPage.collectAsLazyPagingItems()val refreshState = dataPage.loadState.refresh is LoadState.Loadingvar showOption by remember { mutableStateOf(false) }var clickID by remember { mutableStateOf("") }var isLogged by remember { mutableStateOf(CacheHelper.isLogged()) }val coroutineState = rememberCoroutineScope()val pullRefreshState = rememberPullToRefreshState()val listState: LazyListState = rememberLazyListState()LaunchedEffect(Unit) {viewModel.viewEvents.collect {when (it) {is CommonViewEvent.SuccessMessage -> {popupSnackBar(coroutineState,snackBarHostState,label = SNACK_SUCCESS,it.message)}is CommonViewEvent.ErrorMessage -> popupSnackBar(coroutineState,snackBarHostState,label = SNACK_ERROR,it.message)is CommonViewEvent.PopBack -> navController.popBackStack()}}}LaunchedEffect(isLogged) {if (isLogged) {dataPage.refresh()} else {navController.navigate(Screen.Login.route)}}Column(modifier = Modifier.fillMaxSize().background(themeColor).statusBarsPadding().padding(innerPadding)) {TopAppBar(title = {Row(modifier = Modifier.fillMaxWidth(),) {Text(text = "我的收藏",style = TextStyle(fontSize = commonTopBarText,color = white),modifier = Modifier.align(Alignment.CenterVertically),)}},actions = {IconButton(onClick = {RouteUtils.navTo(navCtrl = navController,destinationName = Screen.PoetrySearch.route,isLaunchSingleTop = true)}) {Icon(imageVector = Icons.Default.Search,contentDescription = "搜索",tint = white)}},modifier = Modifier.fillMaxWidth(),colors = TopAppBarDefaults.topAppBarColors(containerColor = themeColor))PullToRefreshBox(isRefreshing = refreshState,onRefresh = { dataPage.refresh() },modifier = Modifier.fillMaxSize().background(white).padding(top = 12.dp),state = pullRefreshState,indicator = {PullToRefreshDefaults.Indicator(state = pullRefreshState,isRefreshing = refreshState,modifier = Modifier.align(Alignment.TopCenter),color = themeColor)}) {LazyColumn(state = listState,modifier = Modifier.fillMaxSize().padding(top = 18.dp, start = 10.dp, bottom = 18.dp, end = 10.dp).background(white10),) {items(dataPage.itemCount) { index ->val item = dataPage[index]if (item != null) {PoetryItem(item.title,item.author,getFirstPoetry(item.paragraphs), isCollect = false,{clickID = item.idshowOption = true}) {RouteUtils.navTo(navCtrl = navController,args = item.id,destinationName = Screen.PoetryDetail.route,isLaunchSingleTop = true)}CommonDevider()} else {// 显示加载中的占位符LoadingItem()}}when (dataPage.loadState.append) {is LoadState.Loading -> {item {LoadingItem()}}is LoadState.Error -> {item {ErrorItem((dataPage.loadState.append as LoadState.Error).error) {dataPage.retry()}}}is LoadState.NotLoading -> {if (dataPage.loadState.append.endOfPaginationReached) {item {NoMoreItem()}}}else -> {}}}}}SelectOptionDialog(showOption, listOf("取消收藏"), { showOption = false }) { index ->viewModel.dispatch(HomeViewAction.UnCollect(clickID))}}@Composable
fun PoetryItem(title: String,author: String,content: String,isCollect: Boolean = false,onLongClick: () -> Unit,onClick: () -> Unit,
) {Row(modifier = Modifier.fillMaxWidth().padding(12.dp).background(Color.White, shape = RoundedCornerShape(8.dp)).pointerInput(Unit) {detectTapGestures(onTap = {onClick.invoke()},onLongPress = {onLongClick.invoke()})},verticalAlignment = Alignment.CenterVertically) {Box(modifier = Modifier.size(80.dp).background(Color(PostAppUtils.getBackgroundColorId(title)),shape = RoundedCornerShape(8.dp)),contentAlignment = Alignment.Center) {Text(text = PostAppUtils.getFirstString(content),color = Color.White,fontSize = 42.sp,fontWeight = FontWeight.Bold)}Spacer(modifier = Modifier.width(10.dp))Column(modifier = Modifier.weight(1f)) {Text(text = title, fontSize = 15.sp, color = Color.Black)Spacer(modifier = Modifier.height(8.dp))Row(modifier = Modifier.fillMaxWidth(),verticalAlignment = Alignment.CenterVertically) {Text(text = author, fontSize = 13.sp, color = Color.Gray)if (isCollect) {Spacer(modifier = Modifier.weight(1f))Text(text = "已收藏",fontSize = 11.sp,color = Color.White,modifier = Modifier.background(themeColor, shape = RoundedCornerShape(4.dp)).padding(horizontal = 6.dp, vertical = 2.dp))}}Spacer(modifier = Modifier.height(5.dp))Text(text = content, fontSize = 12.sp, color = Color.Gray)}}
}