Compose 复杂列表实战优化技巧

2024-12-23 · 30-35 min · 列表优化

在实际项目中,列表是最常见的 UI 组件之一。从简单的文本列表到复杂的卡片列表,从单列到多列网格,从静态内容到动态加载,列表的性能直接影响用户体验。本文深入探讨复杂列表的实战优化技巧,帮助你构建流畅、高效的列表界面。

📚 官方参考

目录

I. 基础优化技巧

掌握基础优化技巧是构建高性能列表的前提。

1.1 正确使用 key

key 是列表优化的基础,没有 key 会导致状态错乱和性能问题。


        // ❌ 错误:没有 key
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn {
        items(items) { item ->
            ItemRow(item)  // 状态会错乱
        }
    }
}

// ✅ 正确:使用稳定的 key
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn {
        items(
            items = items,
            key = { it.id }  // 使用唯一且稳定的 ID
        ) { item ->
            ItemRow(item)
        }
    }
}

// ✅ 更好的做法:使用复合 key
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn {
        items(
            items = items,
            key = { "${it.type}-${it.id}" }  // 类型 + ID 组合
        ) { item ->
            ItemRow(item)
        }
    }
}
        

为什么重要?

1.2 避免在列表项中创建状态

在列表项中创建状态会导致每个项都有独立状态,可能造成内存问题。


        // ❌ 错误:每个列表项都有独立状态
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn {
        items(items) { item ->
            var isExpanded by remember { mutableStateOf(false) }
            // 如果列表有 1000 项,就有 1000 个状态对象
            ExpandableItemRow(item, isExpanded) { 
                isExpanded = !isExpanded 
            }
        }
    }
}

// ✅ 正确:状态提升到列表级别
@Composable
fun ItemList(items: List<Item>) {
    var expandedIds by remember { mutableStateOf(setOf<Long>()) }
    
    LazyColumn {
        items(items) { item ->
            ExpandableItemRow(
                item = item,
                isExpanded = expandedIds.contains(item.id),
                onExpandedChange = { expanded ->
                    expandedIds = if (expanded) {
                        expandedIds + item.id
                    } else {
                        expandedIds - item.id
                    }
                }
            )
        }
    }
}
        

1.3 使用 contentPadding 而非 padding

contentPadding 是 LazyColumn 的专用属性,性能更好。


        // ❌ 错误:使用 padding
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn(
        modifier = Modifier.padding(16.dp)  // 影响整个列表
    ) {
        items(items) { item ->
            ItemRow(item)
        }
    }
}

// ✅ 正确:使用 contentPadding
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn(
        contentPadding = PaddingValues(16.dp)  // 只影响内容区域
    ) {
        items(items) { item ->
            ItemRow(item)
        }
    }
}
        

1.4 合理设置 itemSpacing

适当的间距可以提升视觉体验,但要注意性能。


        // ✅ 正确:使用 itemSpacing
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn(
        verticalArrangement = Arrangement.spacedBy(8.dp)  // 统一间距
    ) {
        items(items) { item ->
            ItemRow(item)
        }
    }
}

// ❌ 避免:在每个 item 中单独设置 margin
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn {
        items(items) { item ->
            ItemRow(
                item = item,
                modifier = Modifier.padding(vertical = 8.dp)  // 不推荐
            )
        }
    }
}
        

II. 复杂布局优化

复杂布局的列表项需要特别注意优化。

2.1 避免深层嵌套

深层嵌套会影响布局性能。


        // ❌ 错误:过度嵌套
@Composable
fun ComplexItemRow(item: Item) {
    Column {
        Row {
            Column {
                Row {
                    Text(item.title)
                }
            }
        }
    }
}

// ✅ 正确:扁平化布局
@Composable
fun OptimizedItemRow(item: Item) {
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Column(modifier = Modifier.weight(1f)) {
            Text(item.title)
            Text(item.subtitle)
        }
        Image(item.avatar)
    }
}
        

2.2 使用 ConstraintLayout 优化复杂布局

对于复杂的布局,ConstraintLayout 可以提供更好的性能。


        // ✅ 使用 ConstraintLayout 优化复杂布局
@Composable
fun ComplexItemRow(item: Item) {
    ConstraintLayout(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    ) {
        val (avatar, title, subtitle, action) = createRefs()
        
        AsyncImage(
            model = item.avatarUrl,
            contentDescription = null,
            modifier = Modifier
                .size(48.dp)
                .constrainAs(avatar) {
                    start.linkTo(parent.start)
                    top.linkTo(parent.top)
                }
        )
        
        Text(
            text = item.title,
            modifier = Modifier.constrainAs(title) {
                start.linkTo(avatar.end, 16.dp)
                top.linkTo(parent.top)
                end.linkTo(action.start, 16.dp)
            }
        )
        
        Text(
            text = item.subtitle,
            modifier = Modifier.constrainAs(subtitle) {
                start.linkTo(title.start)
                top.linkTo(title.bottom, 8.dp)
            }
        )
        
        Button(
            onClick = { },
            modifier = Modifier.constrainAs(action) {
                end.linkTo(parent.end)
                top.linkTo(parent.top)
            }
        ) {
            Text("Action")
        }
    }
}
        

2.3 缓存计算结果

在列表项中避免重复计算。


        // ❌ 错误:每次重组都计算
@Composable
fun ItemRow(item: Item) {
    val formattedPrice = formatPrice(item.price)  // 每次都计算
    val displayName = item.name.uppercase()  // 每次都计算
    
    Row {
        Text(formattedPrice)
        Text(displayName)
    }
}

// ✅ 正确:预先处理数据
data class ProcessedItem(
    val id: Long,
    val formattedPrice: String,
    val displayName: String,
    // ... 其他字段
)

@Composable
fun ItemList(items: List<Item>) {
    val processedItems = remember(items) {
        items.map { item ->
            ProcessedItem(
                id = item.id,
                formattedPrice = formatPrice(item.price),
                displayName = item.name.uppercase(),
                // ...
            )
        }
    }
    
    LazyColumn {
        items(processedItems) { item ->
            ItemRow(item)  // 直接使用处理后的数据
        }
    }
}
        

III. 图片加载优化

图片是列表性能的关键瓶颈,需要特别优化。

3.1 使用 Coil 或 Glide 进行异步加载


        // ✅ 使用 Coil 异步加载
@Composable
fun ItemRow(item: Item) {
    Row {
        AsyncImage(
            model = ImageRequest.Builder(LocalContext.current)
                .data(item.imageUrl)
                .crossfade(true)
                .placeholder(R.drawable.placeholder)
                .error(R.drawable.error)
                .build(),
            contentDescription = null,
            modifier = Modifier.size(64.dp),
            contentScale = ContentScale.Crop
        )
        Text(item.title)
    }
}

// ✅ 使用自定义 Modifier 优化
@Composable
fun OptimizedImage(
    url: String,
    modifier: Modifier = Modifier
) {
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(url)
            .size(Size.ORIGINAL)
            .memoryCachePolicy(CachePolicy.ENABLED)
            .diskCachePolicy(CachePolicy.ENABLED)
            .build(),
        contentDescription = null,
        modifier = modifier,
        contentScale = ContentScale.Crop
    )
}
        

3.2 图片尺寸优化

加载合适尺寸的图片可以显著提升性能。


        // ✅ 根据显示尺寸加载图片
@Composable
fun ItemRow(item: Item) {
    val density = LocalDensity.current
    val imageSize = with(density) { 64.dp.toPx().toInt() }
    
    AsyncImage(
        model = ImageRequest.Builder(LocalContext.current)
            .data(item.imageUrl)
            .size(imageSize)  // 只加载需要的尺寸
            .build(),
        contentDescription = null,
        modifier = Modifier.size(64.dp)
    )
}
        

3.3 使用占位符和过渡动画


        // ✅ 使用占位符和过渡
@Composable
fun ItemRow(item: Item) {
    var imageState by remember { mutableStateOf<ImageLoadState>(ImageLoadState.Loading) }
    
    Box {
        when (imageState) {
            is ImageLoadState.Loading -> {
                Box(
                    modifier = Modifier
                        .size(64.dp)
                        .background(Color.Gray.copy(alpha = 0.3f))
                ) {
                    CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
                }
            }
            is ImageLoadState.Success -> {
                AsyncImage(
                    model = item.imageUrl,
                    contentDescription = null,
                    modifier = Modifier.size(64.dp)
                )
            }
            is ImageLoadState.Error -> {
                Icon(
                    imageVector = Icons.Default.Error,
                    contentDescription = null,
                    modifier = Modifier.size(64.dp)
                )
            }
        }
    }
}
        

IV. 动态内容优化

动态内容的列表需要特别处理。

4.1 使用 itemContentType 优化

itemContentType 可以帮助 Compose 更好地识别列表项类型。


        // ✅ 使用 itemContentType
@Composable
fun ItemList(items: List<Item>) {
    LazyColumn {
        items(
            items = items,
            key = { it.id },
            contentType = { it.type }  // 帮助 Compose 识别类型
        ) { item ->
            when (item.type) {
                ItemType.TEXT -> TextItemRow(item)
                ItemType.IMAGE -> ImageItemRow(item)
                ItemType.VIDEO -> VideoItemRow(item)
            }
        }
    }
}
        

4.2 处理动态高度


        // ✅ 处理动态高度内容
@Composable
fun DynamicHeightItemRow(item: Item) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .animateItemPlacement()  // 平滑过渡
    ) {
        Text(item.title)
        if (item.isExpanded) {
            Text(
                text = item.content,
                modifier = Modifier.animateContentSize()  // 高度动画
            )
        }
    }
}
        

4.3 使用 itemKey 和 itemContentType 组合


        // ✅ 组合使用 key 和 contentType
@Composable
fun MixedItemList(items: List<Item>) {
    LazyColumn {
        items(
            items = items,
            key = { "${it.type}-${it.id}" },
            contentType = { it.type }
        ) { item ->
            when (item.type) {
                ItemType.HEADER -> HeaderRow(item)
                ItemType.ITEM -> ItemRow(item)
                ItemType.FOOTER -> FooterRow(item)
            }
        }
    }
}
        

V. 多类型列表优化

多类型列表是实际项目中的常见需求。

5.1 使用 LazyListScope 构建多类型列表


        // ✅ 使用 LazyListScope 构建多类型列表
@Composable
fun MultiTypeList(
    header: String,
    items: List<Item>,
    footer: String
) {
    LazyColumn {
        // Header
        item(key = "header") {
            HeaderRow(text = header)
        }
        
        // Items
        items(
            items = items,
            key = { it.id }
        ) { item ->
            ItemRow(item)
        }
        
        // Footer
        item(key = "footer") {
            FooterRow(text = footer)
        }
    }
}
        

5.2 分组列表优化


        // ✅ 分组列表优化
@Composable
fun GroupedItemList(groups: List<ItemGroup>) {
    LazyColumn {
        groups.forEach { group ->
            // Group Header
            item(key = "group-${group.id}") {
                GroupHeaderRow(group.title)
            }
            
            // Group Items
            items(
                items = group.items,
                key = { "${group.id}-${it.id}" }
            ) { item ->
                ItemRow(item)
            }
        }
    }
}
        

5.3 使用 StickyHeader 优化分组列表


        // ✅ 使用 stickyHeader 实现粘性头部
@Composable
fun StickyGroupedList(groups: List<ItemGroup>) {
    LazyColumn {
        groups.forEach { group ->
            stickyHeader(key = "header-${group.id}") {
                GroupHeaderRow(group.title)
            }
            
            items(
                items = group.items,
                key = { "${group.id}-${it.id}" }
            ) { item ->
                ItemRow(item)
            }
        }
    }
}
        

VI. 网格列表优化

网格列表需要特殊的优化技巧。

6.1 使用 LazyVerticalGrid


        // ✅ 使用 LazyVerticalGrid
@Composable
fun GridItemList(items: List<Item>) {
    LazyVerticalGrid(
        columns = GridCells.Fixed(2),
        contentPadding = PaddingValues(16.dp),
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        items(
            items = items,
            key = { it.id }
        ) { item ->
            GridItemCard(item)
        }
    }
}

// ✅ 响应式网格
@Composable
fun ResponsiveGridList(items: List<Item>) {
    val configuration = LocalConfiguration.current
    val screenWidth = configuration.screenWidthDp
    
    val columns = when {
        screenWidth > 1200 -> 4
        screenWidth > 800 -> 3
        screenWidth > 600 -> 2
        else -> 1
    }
    
    LazyVerticalGrid(
        columns = GridCells.Fixed(columns),
        contentPadding = PaddingValues(16.dp),
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        verticalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        items(items, key = { it.id }) { item ->
            GridItemCard(item)
        }
    }
}
        

6.2 网格项优化


        // ✅ 优化网格项布局
@Composable
fun GridItemCard(item: Item) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .aspectRatio(0.75f)  // 固定宽高比
    ) {
        Column {
            AsyncImage(
                model = item.imageUrl,
                contentDescription = null,
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f),
                contentScale = ContentScale.Crop
            )
            Column(
                modifier = Modifier.padding(8.dp)
            ) {
                Text(
                    text = item.title,
                    maxLines = 2,
                    overflow = TextOverflow.Ellipsis
                )
                Text(
                    text = item.price,
                    style = MaterialTheme.typography.bodyMedium
                )
            }
        }
    }
}
        

VII. 嵌套列表优化

嵌套列表需要特别注意性能。

7.1 避免深层嵌套 LazyColumn


        // ❌ 错误:深层嵌套 LazyColumn
@Composable
fun NestedListProblem() {
    LazyColumn {
        items(10) { section ->
            LazyColumn {  // 嵌套的 LazyColumn
                items(100) { item ->
                    ItemRow(item)
                }
            }
        }
    }
}

// ✅ 正确:扁平化列表
@Composable
fun FlattenedList(sections: List<Section>) {
    LazyColumn {
        sections.forEach { section ->
            item(key = "section-${section.id}") {
                SectionHeader(section.title)
            }
            items(
                items = section.items,
                key = { "${section.id}-${it.id}" }
            ) { item ->
                ItemRow(item)
            }
        }
    }
}
        

7.2 使用 LazyRow 嵌套


        // ✅ 水平滚动的嵌套列表
@Composable
fun HorizontalNestedList(sections: List<Section>) {
    LazyColumn {
        items(sections, key = { it.id }) { section ->
            Column {
                Text(
                    text = section.title,
                    style = MaterialTheme.typography.h6
                )
                LazyRow(
                    horizontalArrangement = Arrangement.spacedBy(8.dp)
                ) {
                    items(
                        items = section.items,
                        key = { it.id }
                    ) { item ->
                        HorizontalItemCard(item)
                    }
                }
            }
        }
    }
}
        

VIII. 高级优化技巧

8.1 使用 rememberLazyListState 优化滚动


        // ✅ 使用 rememberLazyListState 优化滚动
@Composable
fun OptimizedScrollableList(items: List<Item>) {
    val listState = rememberLazyListState()
    
    // 只在可见项变化时更新
    val visibleItems = remember {
        derivedStateOf {
            val layoutInfo = listState.layoutInfo
            layoutInfo.visibleItemsInfo.map { it.index }
        }
    }
    
    LazyColumn(
        state = listState,
        items = items,
        key = { it.id }
    ) { item ->
        ItemRow(item)
    }
}
        

8.2 预加载优化


        // ✅ 预加载优化
@Composable
fun PreloadList(items: List<Item>) {
    val listState = rememberLazyListState()
    
    LaunchedEffect(listState) {
        snapshotFlow { listState.layoutInfo.visibleItemsInfo }
            .collect { visibleItems ->
                val lastVisibleIndex = visibleItems.lastOrNull()?.index ?: 0
                // 预加载接下来的 5 项
                if (lastVisibleIndex >= items.size - 5) {
                    // 触发加载更多
                }
            }
    }
    
    LazyColumn(state = listState) {
        items(items, key = { it.id }) { item ->
            ItemRow(item)
        }
    }
}
        

8.3 使用 derivedStateOf 优化派生状态


        // ✅ 使用 derivedStateOf 优化派生状态
@Composable
fun FilteredList(allItems: List<Item>, filter: String) {
    val filteredItems by remember(allItems, filter) {
        derivedStateOf {
            allItems.filter { it.title.contains(filter, ignoreCase = true) }
        }
    }
    
    LazyColumn {
        items(filteredItems, key = { it.id }) { item ->
            ItemRow(item)
        }
    }
}
        

8.4 使用 Modifier.onSizeChanged 优化


        // ✅ 使用 onSizeChanged 优化布局
@Composable
fun ResponsiveItemRow(item: Item) {
    var itemWidth by remember { mutableIntStateOf(0) }
    
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .onSizeChanged { size ->
                itemWidth = size.width
            }
    ) {
        if (itemWidth > 600) {
            // 宽屏布局
            WideLayout(item)
        } else {
            // 窄屏布局
            NarrowLayout(item)
        }
    }
}
        

8.5 性能监控


        // ✅ 性能监控
@Composable
fun MonitoredItemList(items: List<Item>) {
    val listState = rememberLazyListState()
    
    LaunchedEffect(listState) {
        snapshotFlow { listState.layoutInfo }
            .collect { layoutInfo ->
                val averageItemSize = layoutInfo.visibleItemsInfo
                    .map { it.size }
                    .average()
                
                Log.d("ListPerformance", "Average item size: $averageItemSize")
                Log.d("ListPerformance", "Visible items: ${layoutInfo.visibleItemsInfo.size}")
            }
    }
    
    LazyColumn(state = listState) {
        items(items, key = { it.id }) { item ->
            ItemRow(item)
        }
    }
}
        

总结

复杂列表优化的核心原则:

掌握这些技巧,可以构建流畅、高效的复杂列表界面。

---

推荐阅读

📚 官方参考
Lists in Compose - Android Developers
Performance best practices - Android Developers
LazyColumn and LazyRow - Android Developers