在实际项目中,列表是最常见的 UI 组件之一。从简单的文本列表到复杂的卡片列表,从单列到多列网格,从静态内容到动态加载,列表的性能直接影响用户体验。本文深入探讨复杂列表的实战优化技巧,帮助你构建流畅、高效的列表界面。
📚 官方参考
- Lists in Compose - Android Developers
- Performance best practices - Android Developers
- LazyColumn and LazyRow - Android Developers
目录
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)
}
}
}
为什么重要?
- key 帮助 Compose 识别列表项的身份
- 没有 key 时,位置变化会导致状态错乱
- 正确的 key 可以避免不必要的重组
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)
}
}
}
总结
复杂列表优化的核心原则:
- ✅ 正确使用 key:确保列表项身份识别
- ✅ 状态提升:避免在列表项中创建状态
- ✅ 预计算数据:在列表外处理数据
- ✅ 图片优化:使用异步加载和缓存
- ✅ 布局优化:避免深层嵌套,使用 ConstraintLayout
- ✅ 类型识别:使用 itemContentType 帮助 Compose
- ✅ 避免嵌套:扁平化列表结构
- ✅ 性能监控:使用工具监控性能
掌握这些技巧,可以构建流畅、高效的复杂列表界面。
---