Android LazyRow的使用

一、核心原理(先理解为什么用)

  • 普通 Row:会一次性渲染所有子组件,即使子项超出屏幕范围,性能差(比如 100 个横向标签,Row 会全部渲染);
  • LazyRow :采用 "懒加载" 机制,仅渲染当前屏幕可见 + 少量 "预加载" 的子项,滑动时回收不可见子项,大幅降低内存占用和绘制耗时,适合长列表 / 动态列表场景。

二、基础用法(入门必会)

1. 最简示例(固定列表)

less 复制代码
@Composable
fun BasicLazyRowDemo() {
    // 模拟数据源
    val dataList = listOf("标签1", "标签2", "标签3", "标签4", "标签5", "标签6", "标签7", "标签8")

    // 基础 LazyRow:横向滚动列表
    LazyRow(
        modifier = Modifier
            .fillMaxWidth() // 宽度占满
            .height(60.dp), // 固定高度(LazyRow 需指定高度,否则默认 wrapContent)
        contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp) // 列表内边距(避免子项贴边)
    ) {
        // 遍历数据源,创建子项(items 是 LazyRow 的核心 DSL)
        items(dataList) { itemText ->
            // 每个子项的布局(示例:圆角标签)
            Text(
                text = itemText,
                modifier = Modifier
                    .padding(end = 8.dp) // 子项间距
                    .background(Color.LightGray, RoundedCornerShape(20.dp))
                    .padding(horizontal = 16.dp, vertical = 8.dp),
                textAlign = TextAlign.Center
            )
        }
    }
}

效果:横向滚动的标签列表,仅渲染可见的标签,滑动时动态加载 / 回收子项。

2. 核心 DSL 说明

LazyRow 的内容通过LazyListScope DSL 构建,核心函数:

函数 用途 示例
items(list) 遍历集合创建子项(最常用) items(dataList) { item -> ... }
items(count) 按数量创建子项(无数据源场景) items(100) { index -> ... }
item() 添加单个子项(列表头部 / 尾部) item { Text("头部") }

示例:混合单个子项 + 列表项

ini 复制代码
LazyRow {
    // 单个头部项
    item {
        Text(
            text = "推荐标签",
            modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
            fontWeight = FontWeight.Bold
        )
    }
    // 列表项
    items(dataList) { itemText ->
        // 子项布局...
    }
}

三、关键属性详解(核心配置)

LazyRow 的参数决定了列表的滚动行为、对齐方式、间距等,核心参数如下:

less 复制代码
LazyRow(
modifier: Modifier = Modifier, // 布局修饰符(宽高、背景等)
state: LazyListState = rememberLazyListState(), // 列表状态(滚动位置、是否滑动等)
contentPadding: PaddingValues = PaddingValues(0.dp), // 列表内边距(避免子项贴边)
reverseLayout: Boolean = false, // 反转布局(子项从右往左排列)
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, // 子项水平排列方式
verticalAlignment: Alignment.Vertical = Alignment.Top, // 子项垂直对齐方式
flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), // 滑动惯性行为
userScrollEnabled: Boolean = true, // 是否允许用户滑动
content: LazyListScope.() -> Unit // 子项内容
)

1. 常用属性实战

(1)控制子项间距 & 对齐
ini 复制代码
LazyRow(
modifier = Modifier.fillMaxWidth().height(80.dp),
contentPadding = PaddingValues(horizontal = 16.dp), // 列表左右内边距
horizontalArrangement = Arrangement.spacedBy(12.dp), // 子项之间的间距(替代手动 padding)
verticalAlignment = Alignment.CenterVertically // 子项垂直居中
) {
    items(dataList) { itemText ->
        Text(
            text = itemText,
            modifier = Modifier
                .background(Color.Blue.copy(alpha = 0.1f), RoundedCornerShape(8.dp))
                .padding(horizontal = 16.dp, vertical = 8.dp)
        )
    }
}
(2)列表状态(LazyListState)

用于控制 / 监听列表滚动 (比如滚动到指定位置、获取当前滚动偏移),需用 rememberLazyListState()缓存状态:

ini 复制代码
@Composable
fun LazyRowWithState() {
    val dataList = listOf("标签1", "标签2", "标签3","标签4","标签5","标签6","标签7", "标签8", "标签9", "标签100")
    // 记住列表状态(避免重组时重置)
    val lazyListState = rememberLazyListState()
    // 协程作用域(用于滚动操作)
    val coroutineScope = rememberCoroutineScope()

    Column {
        // 控制按钮:滚动到第5个位置
        Button(
            onClick = {
                coroutineScope.launch {
                    // 滚动到指定索引的子项(animateScrollToItem 带动画,scrollToItem 无动画)
                    lazyListState.animateScrollToItem(index = 5)
                }
            },
            modifier = Modifier.padding(16.dp)
        ) {
            Text("滚动到第6个标签")
        }

        // LazyRow 绑定状态
        LazyRow(
            state = lazyListState,
            modifier = Modifier.fillMaxWidth().height(60.dp)
        ) {
            items(dataList) { itemText ->
                Text(
                    text = itemText,
                    modifier = Modifier.padding(end = 8.dp).background(Color.LightGray).padding(8.dp)
                )
            }
        }
    }
}
(3)反转布局(reverseLayout)

设置 reverseLayout = true,子项从右往左排列(适合 RTL 语言 / 反向滚动场景):

scss 复制代码
LazyRow(
reverseLayout = true, // 子项从右到左排列
modifier = Modifier.fillMaxWidth().height(60.dp)
) {
    items(dataList) { itemText ->
        Text(text = itemText, modifier = Modifier.padding(start = 8.dp)) // 注意间距改为 start
    }
}

四、高级使用场景

场景 1:带点击事件的子项(如横向商品列表)

scss 复制代码
@Composable
fun LazyRowWithClick() {
    // 模拟商品数据
    data class Product(val id: Int, val name: String, val price: String)
    val productList = listOf(
        Product(1, "手机", "¥2999"),
        Product(2, "耳机", "¥199"),
        Product(3, "平板", "¥1999"),
        Product(4, "手表", "¥899")
    )

    LazyRow(
        modifier = Modifier.fillMaxWidth().height(200.dp),
        contentPadding = PaddingValues(horizontal = 16.dp),
        horizontalArrangement = Arrangement.spacedBy(12.dp)
    ) {
        items(productList) { product ->
            // 商品卡片(可点击)
            Column(
                modifier = Modifier
                    .width(120.dp)
                    .clickable {
                        // 点击事件:跳转到商品详情
                        println("点击商品:${product.name}")
                    }
                    .background(Color.White, RoundedCornerShape(8.dp))
                    .padding(8.dp),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center
            ) {
                // 占位图片(实际项目用 Coil/Glide 加载)
                Box(
                    modifier = Modifier
                        .size(80.dp)
                        .background(Color.LightGray),
                    contentAlignment = Alignment.Center
                ) {
                    Text("图片")
                }
                Text(text = product.name, fontSize = 14.sp, modifier = Modifier.padding(top = 8.dp))
                Text(text = product.price, fontSize = 12.sp, color = Color.Red, modifier = Modifier.padding(top = 4.dp))
            }
        }
    }
}

场景 2:网格横向列表(LazyGrid + 横向)

如果需要横向的网格列表(比如 2 行 N 列),用 LazyHorizontalGrid(替代 LazyRow + 嵌套 Column):

ini 复制代码
@Composable
fun LazyHorizontalGridDemo() {
    val dataList = (1..20).map { "Item $it" }

    LazyHorizontalGrid(
        rows = GridCells.Fixed(2), // 固定2行
        modifier = Modifier.fillMaxWidth().height(120.dp),
        contentPadding = PaddingValues(16.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp),
        verticalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        items(dataList) { item ->
            Text(
                text = item,
                modifier = Modifier
                    .background(Color.LightGray, RoundedCornerShape(4.dp))
                    .padding(8.dp)
                    .fillMaxWidth(),
                textAlign = TextAlign.Center
            )
        }
    }
}

场景 3:无限滚动列表(加载更多)

结合 LazyListState 监听滚动到底部,实现 "加载更多":

scss 复制代码
@Composable
fun LazyRowLoadMore() {
    var dataList by remember { mutableStateOf((1..20).map { "Item $it" }) }
    val lazyListState = rememberLazyListState()
    var isLoading by remember { mutableStateOf(false) }
    val coroutineScope = rememberCoroutineScope()

    // 监听滚动:当滚动到倒数第3项时,加载更多
    LaunchedEffect(lazyListState) {
        snapshotFlow {
            // 获取最后一个可见项的索引
            lazyListState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
        }.collect { lastVisibleIndex ->
            val totalItems = dataList.size
            // 滚动到倒数第3项,且未加载中
            if (lastVisibleIndex >= totalItems - 3 && !isLoading) {
                isLoading = true
                // 模拟网络请求(延迟1秒)
                coroutineScope.launch {
                    delay(1000)
                    // 追加数据
                    dataList = dataList + (totalItems + 1..totalItems + 10).map { "Item $it" }
                    isLoading = false
                }
            }
        }
    }

    LazyRow(
        state = lazyListState,
        modifier = Modifier.fillMaxWidth().height(60.dp),
        contentPadding = PaddingValues(16.dp)
    ) {
        items(dataList) { item ->
            Text(
                text = item,
                modifier = Modifier
                    .padding(end = 8.dp)
                    .background(Color.LightGray)
                    .padding(8.dp)
            )
        }
        // 加载中占位项
        if (isLoading) {
            item {
                CircularProgressIndicator(
                    modifier = Modifier.padding(start = 8.dp),
                    strokeWidth = 2.dp,
                    size = 20.dp
                )
            }
        }
    }
}

五、性能优化(避坑关键)

  1. 复用子项 Key :默认情况下,LazyRow 按索引复用子项,若数据源发生增删 / 排序,会导致子项错误复用。解决方案:给 items 指定唯一 key
javascript 复制代码
// 正确:用数据唯一ID作为key
items(dataList, key = { item -> item.id }) { item ->
    // 子项布局...
}
  1. 减少重组
  • 子项布局抽离为独立 Composable 函数,并使用 remember 缓存不变数据;
  • 避免在 items 内部创建新对象(如 Modifier/Color),否则会触发频繁重组:
scss 复制代码
// 错误:每次重组都会创建新的 Shape
    items(dataList) {
        Text(modifier = Modifier.background(Color.Gray, RoundedCornerShape(8.dp)))
    }

    // 正确:缓存 Shape
    val shape = remember { RoundedCornerShape(8.dp) }
    items(dataList) {
        Text(modifier = Modifier.background(Color.Gray, shape))
    }
  1. 控制预加载数量 :通过 flingBehavior 调整预加载数量(默认预加载 1-2 个屏幕的子项),减少内存占用:
ini 复制代码
LazyRow(
flingBehavior = ScrollableDefaults.flingBehavior(
state = lazyListState,
// 预加载距离(越小,预加载越少)
snapAnimationSpec = SnapSpec(dampingRatio = 0.8f)
)
) {
    // 子项...
}
  1. 避免子项无限宽高 :LazyRow 子项需指定固定宽度 / 最大宽度,否则会导致测量异常(比如 fillMaxWidth() 不适合 LazyRow 子项,改用 width()/size())。

六、常见问题 & 解决方案

问题 原因 解决方案
子项内容溢出 子项宽度超过 LazyRow 高度 / 宽度 给子项设置固定尺寸,或用 wrapContent
滚动卡顿 子项布局复杂 / 未复用 Key 简化子项布局、添加唯一 Key
滚动到指定位置无效 未在协程中执行 scrollToItem rememberCoroutineScope 包裹
列表内边距无效 用了 modifier.padding 而非 contentPadding 改用 contentPadding 设置内边距
相关推荐
阿巴斯甜4 小时前
Android Row 的使用
android jetpack
林栩link4 小时前
Now in Android 现代应用开发实践(三):架构设计(UI)
android·android jetpack
段娇娇5 小时前
Android jetpack LiveData (二) 原理篇
android·android jetpack
阿巴斯甜8 小时前
Android LazyColumn的使用
android jetpack
阿巴斯甜1 天前
Compose 内置的 Modifier 用法总结
android jetpack
simplepeng1 天前
TikTok 通过 Jetpack Compose 将代码大小减少 58%,并提升了新功能的 app 性能
android·android jetpack
BoomHe1 天前
Kotlin shareIn 和 stateIn 使用场景
android·kotlin·android jetpack
俩个逗号。。2 天前
Compose 预览报错:java.lang.NoSuchMethodError
android·android jetpack
黄林晴2 天前
Android Room 3.0 来了,这次改得有点狠
android·android jetpack