Jetpack Compose分类电影列表:异步与懒加载问题求助
Jetpack Compose 电影分类列表实现方案
一、状态管理重构
首先修正原ViewModel的状态问题,每个分类需要独立管理自身的影片数据、加载状态和错误信息,同时让Compose能感知状态变化:
1. 定义包装数据类
// 包装分类及其关联的影片状态 data class CategoryWithMovies( val category: Category, var movies: List<Movie> = emptyList(), var isLoading: Boolean = false, var error: String? = null ) // 示例Movie类(根据实际业务调整) data class Movie(val id: Int, val title: String, val posterUrl: String)
2. 重构ViewModel
class CatalogViewModel : ViewModel() { // 用mutableStateListOf让Compose感知列表项的状态变化 private val _categoriesWithMovies = mutableStateListOf<CategoryWithMovies>() val categoriesWithMovies: List<CategoryWithMovies> get() = _categoriesWithMovies var isCategoriesLoading by mutableStateOf(false) var categoriesLoadError by mutableStateOf<String?>(null) init { fetchCategories() } private fun fetchCategories() { isCategoriesLoading = true categoriesLoadError = null Api.Call( completionHandler = object : completionHandler<ArrayList<Category>, PKError> { override fun succeed(result: ArrayList<Category>?) { isCategoriesLoading = false result?.let { categoryList -> // 将原始分类转换为带状态的包装类 _categoriesWithMovies.addAll( categoryList.map { CategoryWithMovies(category = it) } ) } } override fun failed(error: PKError?) { isCategoriesLoading = false categoriesLoadError = error?.message ?: "加载分类失败" } } ) } // 加载指定分类的影片,避免重复请求 fun fetchMoviesForCategory(categoryId: Int) { val categoryItem = _categoriesWithMovies.find { it.category.id == categoryId } ?: return if (categoryItem.isLoading || categoryItem.movies.isNotEmpty()) return categoryItem.isLoading = true categoryItem.error = null // 调用按分类获取影片的API(需根据实际API调整) Api.CallMoviesByCategory( categoryId = categoryId, completionHandler = object : completionHandler<ArrayList<Movie>, PKError> { override fun succeed(result: ArrayList<Movie>?) { categoryItem.isLoading = false result?.let { categoryItem.movies = it } } override fun failed(error: PKError?) { categoryItem.isLoading = false categoryItem.error = error?.message ?: "加载影片失败" } } ) } }
二、UI层实现
用LazyColumn展示分类,每个分类下嵌套LazyRow展示影片,同时处理加载、错误状态:
@Composable fun CatalogScreen(viewModel: CatalogViewModel = viewModel()) { val lazyListState = rememberLazyListState() // 监听可见分类,触发影片加载 LaunchedEffect(lazyListState.layoutInfo.visibleItemsInfo) { val visibleCategoryIds = lazyListState.layoutInfo.visibleItemsInfo .mapNotNull { item -> viewModel.categoriesWithMovies.getOrNull(item.index)?.category?.id } visibleCategoryIds.forEach { viewModel.fetchMoviesForCategory(it) } } when { viewModel.isCategoriesLoading -> { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } } viewModel.categoriesLoadError != null -> { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text(text = viewModel.categoriesLoadError ?: "加载失败", color = Color.Red) } } else -> { LazyColumn(state = lazyListState) { items(viewModel.categoriesWithMovies) { categoryItem -> CategorySection(categoryItem = categoryItem) } } } } } @Composable fun CategorySection(categoryItem: CategoryWithMovies) { Column(modifier = Modifier.fillMaxWidth()) { // 分类标题 Text( text = categoryItem.category.name, fontSize = 18.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(16.dp) ) when { categoryItem.isLoading -> { Box(modifier = Modifier.height(120.dp), contentAlignment = Alignment.Center) { CircularProgressIndicator() } } categoryItem.error != null -> { Box(modifier = Modifier.height(120.dp), contentAlignment = Alignment.Center) { Text(text = categoryItem.error ?: "加载失败", color = Color.Red) } } categoryItem.movies.isNotEmpty() -> { LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp) ) { items(categoryItem.movies) { movie -> MovieItem(movie = movie) } } } else -> { Box(modifier = Modifier.height(120.dp), contentAlignment = Alignment.Center) { Text(text = "暂无影片") } } } Divider(modifier = Modifier.padding(vertical = 8.dp)) } } @Composable fun MovieItem(movie: Movie) { Column(modifier = Modifier.width(100.dp)) { Image( painter = rememberAsyncImagePainter(model = movie.posterUrl), contentDescription = movie.title, modifier = Modifier .fillMaxWidth() .aspectRatio(2f / 3f) .clip(RoundedCornerShape(8.dp)) ) Text( text = movie.title, fontSize = 12.sp, maxLines = 2, overflow = TextOverflow.Ellipsis, modifier = Modifier.padding(top = 4.dp) ) } }
三、核心优化点说明
- 按需加载:通过
LazyListState监听屏幕可见的分类项,仅对可见分类发起影片API请求,避免一次性加载所有分类数据,优化网络和内存占用。 - 状态隔离:每个分类独立管理自身的加载、错误、影片数据,避免全局状态混乱,Compose能精准重组对应UI。
- 请求防抖:在
fetchMoviesForCategory中判断加载状态和已有数据,避免重复发起相同请求。
内容的提问来源于stack exchange,提问作者XCarb
相关产品推荐
相关产品推荐

