Jetpack Compose中的列表控件LazyRow和LazyColumn详解

news/2025/3/30 1:05:57/

背景

如果你需要显示大量的条目(或一个未知长度的列表),使用像 Column 这样的布局会导致性能问题,因为所有的条目都会被组合和布局,无论它们是否可见。那么,在Compose中有没有像RecycleView的控件可以滑动呢?答案:是有的,它们分别是LazyRow和LazyColumn。

基本使用

LazyRow和LazyColumn用法相似,只是展示的方向不同,所以便是不各自分个章节出来了,下文以LazyColumn为例讲解

@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true)
@Composable
fun ListPageDemo() {//可触发重组的Listval list = arrayListOf<String>()//构造数据repeat(30) {list.add("卡片$it")}ComposeDemoTheme {Column() {LazyColumn {items(list) {Text(it, modifier = Modifier.fillMaxWidth().height(50.dp))}}}}}
效果如下所示:

上面主要使用了LazyListScope里提供的items方法来构造列表

除此之外,LazyListScope也是提供了几个不同的方法来构造列表

LazyColumn {// 添加单个项目item {Text(text = "First item")}// 添加五个项目items(5) { index ->Text(text = "Item: $index")}// 添加其他单个项目item {Text(text = "Last item")}}

可观察数据列表 mutableStateListOf()

上面那种,由于我们是使用的基本数据类型的ArrayList,所以在列表数据发生变更时,不会触发重组 如果我们想要实现可触发重组的数据列表,可以使用Compose中提供的mutableStateListOf()方法来创建数据列表 如下面例子:

@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true)
@Composable
fun ListPageDemo() {//可触发重组的Listval list = mutableStateListOf<String>()repeat(30) {list.add("卡片$it")}ComposeDemoTheme {Box(modifier = Modifier) {Column() {LazyColumn {items(list) {Text(it, modifier = Modifier.fillMaxWidth().height(50.dp))}}}//设置靠右下角Column(horizontalAlignment = Alignment.End,verticalArrangement = Arrangement.Bottom,modifier = Modifier.fillMaxSize().padding(end = 16.dp,bottom = 16.dp)) {FloatingActionButton(onClick = {//移除列表最后一个数据list.removeLast()}) {Icon(imageVector = Icons.Default.Clear,contentDescription = null)}FloatingActionButton(onClick = {//添加一个新的数据val time = System.currentTimeMillis()list.add(time.toString())}) {Icon(imageVector = Icons.Default.Add,contentDescription = null)}}}}
}

为了方便演示,加了两个悬浮按钮,用来测试数据的增加和删除,

效果如下所示:

mutableStateListOf()方法返回的类型是SnapshotStateList,此类型和ArrayList一样,有着相关的添加,移除数据等方法,同时还能触发Compose中的重组操作

属性

我们从构造方法来看下LazyColumn具有什么参数可以设置

fun LazyColumn(modifier: Modifier = Modifier,state: LazyListState = rememberLazyListState(),contentPadding: PaddingValues = PaddingValues(0.dp),reverseLayout: Boolean = false,verticalArrangement: Arrangement.Vertical =if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,horizontalAlignment: Alignment.Horizontal = Alignment.Start,flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),content: LazyListScope.() -> Unit
){}

modifier想必也不用多说了,不清楚了可以看下前面的文章

FlingBehavior这个属性是用于定义滑动动作释放后的速度变化逻辑的,比如,当滑动动作释放后,列表还将继续滑动,速度依时递减

此属性有点不太常用,就不讲解了

由于state这个属性涉及东西较多,所以单独放在后面讲解

contentPadding

此属性主要是用来设置内边距的,取值为PaddingValues

PaddingValues方法的参数有三种,根据需要选择即可:

PaddingValues(all:Dp)

PaddingValues(horizontal: Dp, vertical: Dp)

PaddingValues(start: Dp = 0.dp,top: Dp = 0.dp,end: Dp = 0.dp,bottom: Dp = 0.dp)

示例代码(设置内边距为16dp):

LazyColumn(contentPadding = PaddingValues(16.dp)) {items(list) {Text(it, modifier = Modifier.fillMaxWidth().height(50.dp))}
}

效果:

reverseLayout

将列表顺序反转过来,接收一个boolean数值

示例代码:

LazyColumn(reverseLayout = true) {items(list) {Text(it, modifier = Modifier.fillMaxWidth().height(50.dp))}
}

 效

verticalArrangement


此属性组主要是用来设置item的相互间距,针对的是LazyColumn

注意:LazyRow则是horizontalArrangement属性

LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {items(list) {Text(it, modifier = Modifier.fillMaxWidth().height(50.dp).background(color = Color.Yellow))}
}

效果:

 horizontalAlignment


设置水平对齐方式,是针对LazyColumn

注意:LazyRow中的属性则是verticalAlignment

LazyColumn(Modifier.fillMaxWidth(),horizontalAlignment = Alignment.End) {items(list) {Text(it, modifier = Modifier.height(50.dp).background(color = Color.Yellow))}
}

注意:要设置LazyColumn为填充最大宽度,然后item项是没有最大宽度的,才会看到效果

效果:

 

 

state

接收LazyListState对象,主要是提供一个可观察的状态,用来实现控制和观察列表组件,如滚动到列表某一项的时候,需要展示一个悬浮按钮等逻辑

LazyListState有以下常用属性:

firstVisibleItemIndex 当前页面列表显示的第一项的下标

firstVisibleItemScrollOffset 当前页面列表显示的第一项的滑动偏移量

interactionSource 当列表被拖拽时候,会触发对应的分发事件,interactionSource存放着相关的事件state

layoutInfo 列表布局相关信息

isScrollInProgress 一个boolean数值,标识当前列表是否处于滑动状态

对滚动位置做出反应示例

我们以firstVisibleItemIndex为例,可以实现滚动到列表某一项的时候,需要展示一个悬浮按钮的功能

要实现上述功能,肯定得要一个boolean的state对象才行,但firstVisibleItemIndex只是一个Int类型,如何将其转换为可观察的boolean数值(MutableState<Boolean>)呢?

这里可以使用derivedStateOf()方法来进行转换

val state = rememberLazyListState()
val showButton by remember {derivedStateOf {state.firstVisibleItemIndex > 5}
}

示例代码:

@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true)
@Composable
fun ListPageDemo() {//可触发重组的Listval list = mutableStateListOf<String>()repeat(30) {list.add("卡片$it")}val state = rememberLazyListState()val showButton by remember {derivedStateOf {state.firstVisibleItemIndex > 5}}ComposeDemoTheme {Box(modifier = Modifier) {Column() {LazyColumn(state = state,modifier = Modifier.fillMaxWidth()) {items(list) {Text(it, modifier = Modifier.height(50.dp).background(color = Color.Yellow))}}}if (showButton) {//这里由于要设置悬浮按钮的位置,所以外层需要一个Box布局Box(Modifier.fillMaxSize().padding(bottom = 16.dp),contentAlignment = Alignment.BottomCenter) {FloatingActionButton(onClick = {}) {Icon(imageVector = Icons.Default.KeyboardArrowUp,contentDescription = null)}}}//设置靠右下角Column(horizontalAlignment = Alignment.End,verticalArrangement = Arrangement.Bottom,modifier = Modifier.fillMaxSize().padding(end = 16.dp, bottom = 16.dp)) {FloatingActionButton(onClick = {//移除列表最后一个数据list.removeLast()}) {Icon(imageVector = Icons.Default.Clear,contentDescription = null)}FloatingActionButton(onClick = {//添加一个新的数据val time = System.currentTimeMillis()list.add(time.toString())}) {Icon(imageVector = Icons.Default.Add,contentDescription = null)}}}}
}

可以从下图看到,当列表的第一项为卡片6的时候,按钮即显示出来了:

控制滚动

除此之外,LazyListState还提供了一些方法,可以让我们控制列表自动滚动 scrollToItem(index:Int,scrollOffset: Int = 0) 滚动到指定的数据项 animateScrollToItem(index:Int,scrollOffset: Int = 0) 平滑滚动到指定的数据项

注意:这两个方法都是挂起方法,需要在协程中使用

我们基于上面的例子,加以改造下,实现点击悬浮按钮,列表滚动回顶部的功能

为例方便阅读,下面的代码稍微省略了一些不重要的代码:

@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true)
@Composable
fun ListPageDemo() {...// 记住一个协程作用域,以便能够启动滚动操作val coroutineScope = rememberCoroutineScope()FloatingActionButton(onClick = {coroutineScope.launch {//滚动到第一项state.animateScrollToItem(0)}}) {Icon(imageVector = Icons.Default.KeyboardArrowUp,contentDescription = null)}
}

这里需要注意的是,使用滚动得通过协程来进行调用,通过rememberCoroutineScope()来得到一个协程作用域对象

效果如下图所示:

高级使用

1、粘性标题

LazyListScope除了items等方法,还有stickyHeader方法,帮助我们快速实现粘性标题的效果,如下图所示:

代码:

//可触发重组的List
val list = mutableStateListOf<String>()repeat(30) {list.add("title1 卡片$it")
}//可触发重组的List
val list1 = mutableStateListOf<String>()repeat(30) {list1.add("title2 卡片$it")
}LazyColumn(state = state, modifier = Modifier.fillMaxWidth()) {stickyHeader {Text("title1",modifier = Modifier.fillMaxWidth().background(color = Color.Green))}items(list){Text(it, modifier = Modifier.height(50.dp).background(color = Color.Yellow))}stickyHeader {Text("title2",modifier = Modifier.fillMaxWidth().background(color = Color.Green))}items(list1){Text(it, modifier = Modifier.height(50.dp).background(color = Color.Yellow))}}

上面与之前的代码,多了一个数据源list1来,当然还可以把代码通过循环来精简一下,这里不再过多补充了

2、item动画

animateItemPlacement 但item重新排序的动画

示例代码:

LazyColumn {items(books, key = { it.id }) {Row(Modifier.animateItemPlacement()) {// ...}}
}

自定义动画:

LazyColumn {items(books, key = { it.id }) {Row(Modifier.animateItemPlacement(tween(durationMillis = 250))) {// ...}}
}

3、分页

借助 Paging 库,应用可以支持包含大量列表项的列表,根据需要加载和显示小块的列表。Paging 3.0 及更高版本通过 androidx.paging:paging-compose 库提供 Compose 支持。

如需显示分页内容列表,可以使用 collectAsLazyPagingItems() 扩展函数,然后将返回的 LazyPagingItems 传入 LazyColumn 中的 items()。

与视图中的 Paging 支持类似,您可以通过检查 item 是否为 null,在加载数据时显示占位符

import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items@Composable
fun MessageList(pager: Pager<Int, Message>) {val lazyPagingItems = pager.flow.collectAsLazyPagingItems()LazyColumn {items(items = lazyPagingItems,// The key is important so the Lazy list can remember your// scroll position when more items are fetched!key = { message -> message.id }) { message ->if (message != null) {MessageRow(message)} else {MessagePlaceholder()}}}
}

更优雅使用列表组件

1、item绑定key

在上文开始也提到,我们是使用LazyListScope里的items方法来构建数据项的,但是我们并没有为我们的每一项数据设置一个key,所以会导致以下问题:

如果数据集发生变化,这可能会导致问题,因为改变位置的 item 会失去任何记忆中的状态。 如果你想象一下 LazyRow 在LazyColumn 中的情景,如果该行改变了 item 的位置,用户就会失去他们在该行中的滚动位置。

所以这个时候,我们可以通过items方法里的keys参数来进行设置

此参数接收一个lambda表达式(item: T) -> Any

这里的Any是这里可返回任何类型的数据,但必须要要所提供的数据必须能够被存储在一个Bundle中,具体可点击链接查看该类文档

LazyColumn(state = state, modifier = Modifier.fillMaxWidth()) {items(list, key = {//这里可返回任何类型的数据(Any),但必须要要所提供的数据必须能够被存储在一个 Bundle 中//直接使用本身作为字符串it}) {Text(it, modifier = Modifier.height(50.dp).background(color = Color.Yellow))}
}

2、使用contentType更好复用组件

如果需要复用数据项内容时,可使用contentType来标明类型,如下的示例代码

LazyColumn {items(elements, contentType = { it.type }) {// ...}
}

3、item的注意项

item最好定义固定宽高

例如,当您希望在后期阶段异步检索一些数据(例如图片)以填充列表项时。 这会使延迟布局在首次衡量时组合其所有项,因为项的高度为 0 像素,所以这类项可完全适合视口大小。 待这些项加载完毕且高度增加后,延迟布局随后会舍弃首次不必要组合起来的所有其他项,因为这些项实际上无法适合视口。

@Composable
fun Item() {Image(painter = rememberImagePainter(data = imageUrl),modifier = Modifier.size(30.dp),// ...)
}

建议多个元素放入一个项中

LazyColumn(// ...
) {item { Item(0) }item {Item(1)Divider()}item { Item(2) }// ...
}

避免嵌套可向同一方向滚动的组件

如下不推荐的代码:

// Throws IllegalStateException
Column(modifier = Modifier.verticalScroll(state)
) {LazyColumn {// ...}
}


http://www.ppmy.cn/news/70718.html

相关文章

Unity用AI制作天空盒,并使用,详细图文教程

Unity用AI制作天空盒&#xff0c;并使用&#xff0c;详细图文教程 效果AI制作使用总结版权声明 效果 先上我自己做的效果 AI制作 首先登录AI制作的网站&#xff0c;打开就可以用&#xff0c;不需要登录 这是网址&#xff1a;https://skybox.blockadelabs.com/ 1.创建新的 2…

GraphPad Prism 9.5.1 for Mac 操作简便功能强大且实用的医学绘图分析工具

GraphPad Prism简介 GraphPad Prism是一款非常实用的统计软件&#xff0c;其功能非常强大&#xff0c;能够帮助用户进行各类科研数据的处理和分析&#xff0c;快速绘制出各种专业的图像和数据报告。 GraphPad Prism软件的用户界面非常友好&#xff0c;易于学习和操作&#xf…

java并发-ReentrantLock

当多个线程需要同时对共享资源进行操作时&#xff0c;就需要用到线程同步技术。Java中提供了synchronized关键字用于线程同步&#xff0c;而ReentrantLock就是另外一种用于线程同步的技术&#xff0c;本文将介绍ReentrantLock及其使用方法。 ### 1. 概述 ReentrantLock是Java…

飞凌嵌入式技术帖——i.MX9352的GPIO怎么用?

之前小编为大家介绍过在飞凌嵌入式i.MX6ULL开发板上操作GPIO的方法。本期&#xff0c;让我们一起走近i.MX9352处理器&#xff0c;深入了解这位i.MX系列新成员的GPIO该如何操作&#xff0c;以及它与前辈i.MX6ULL处理器又有哪些异同。 01 硬件原理分析 以点灯和按键为例&#xf…

计算机组成原理基础练习题第三章总线

1. 计算机使用总线结构便于增减外设,同时()。A、减少了信息传输量B、提高了信息的传输速度C、减少了信息传输线的条数D、上述各项均不对 2. 计算机使用总线结构的主要优点是便于实现积木化,缺点是()。A、地址信息、数据信息和控制信息不能同时出现 B、地址信息与数据…

Go语言必会面试题

文章目录 1.下面代码有什么问题吗&#xff1f;2.下面代码输出什么&#xff0c;请说明。3.关于 slice 或 map 操作&#xff0c;下面正确的是&#xff1f;4.下面代码输出什么&#xff1f;5.关于字符串连接&#xff0c;下面语法正确的是&#xff1f;6.下面代码能编译通过吗&#x…

Java抽象类与面向抽象编程

抽象类 如果一个class定义了方法&#xff0c;但没有具体执行代码&#xff0c;这个方法就是抽象方法&#xff0c;抽象方法用abstract修饰。 因为无法执行抽象方法&#xff0c;因此这个类也必须申明为抽象类&#xff08;abstract class&#xff09;。 使用abstract修饰的类就是…

宝塔面板webhook 使用教程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 背景1、介绍一下Webhook2、使用步骤1.安装git2.安装WebHook3.添加WebHook4.配置git 钩子 &#xff08;码云示例&#xff09;5.私有项目还需要做以下操作 背景 最近…