You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 12:10:54