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

news2025/4/19 19:50:43

背景

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

基本使用

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

@SuppressLint("UnrememberedMutableState")
@Preview(showBackground = true)
@Composable
fun ListPageDemo() {

    //可触发重组的List
    val 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() {

    //可触发重组的List
    val 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() {

    //可触发重组的List
    val 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.coloradmin.cn/o/542443.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python类的高级函数

类的高级函数 __str__ 如果定义了该函数&#xff0c;当print当前实例化对象的时候&#xff0c;会返回该函数的return信息 用法&#xff1a; def __str__(self): return str_type 参数&#xff1a;无 返回值&#xff1a;一般返回对于该类的描述信息 __getattr__ 当调用的…

C++类模板——嵌套使用

目录 类模板的嵌套分类 一、数组的成员是栈 &#xff08;一&#xff09;Vector大小为2&#xff0c;Stack大小为3&#xff1b;不对Vector进行扩展&#xff1b;&#xff08;浅拷贝&#xff09;&#xff0c;只对C内置数据类型进行拷贝 1&#xff09;代码 2&#xff09;注意事…

代码随想录算法训练营第四十五天 | dp最小值题目

70. 爬楼梯 的改进版 改进条件 改为&#xff1a;一步一个台阶&#xff0c;两个台阶&#xff0c;三个台阶&#xff0c;…&#xff0c;直到 m个台阶。问有多少种不同的方法可以爬到楼顶呢&#xff1f; 文档讲解&#xff1a;代码随想录 (programmercarl.com) 状态&#xff1a;能…

IS420ESWBH3A DIN导轨或潜在面板安装

马克维耶斯IONet交换机IS420ESWBH3A由通用电气公司生产&#xff0c;工作在24/28 VDC&#xff0c;这种型号没有任何光纤端口&#xff0c;但有16个铜端口。IS420ESWBH3A开关在DIN导轨安装时需要安装夹&#xff0c;在面板安装时只需要螺钉。 IS420ESWBH3A技术规范 光纤端口数量没…

chatgpt赋能Python-python3_10安装numpy

Python3.10安装numpy&#xff1a;一步一步教你如何轻松完成 Python3.10虽然已经发布了&#xff0c;但是有些模块还需要手动安装&#xff0c;例如numpy。在这篇文章中&#xff0c;我们将会详细介绍如何安装numpy模块&#xff0c;以及为什么要使用numpy模块。 什么是numpy模块&…

Redis教程-基础篇

第一部分&#xff1a;入门 1.1 Redis简介 Redis&#xff08;Remote Dictionary Server&#xff09;是一个高性能的键值存储系统&#xff0c;它是一种开源、基于内存的数据结构服务器。Redis以其出色的性能和灵活的数据结构而受到广泛关注和应用。下面展开描述Redis的定义和特…

chatgpt赋能Python-python3gui

Python3 GUI- 让你的应用程序更酷炫 随着技术的发展&#xff0c;图形用户界面(Graphical User Interface, GUI)已经成为软件开发过程中不可或缺的一部分。Python3是一个用于快速开发应用程序的强大编程语言&#xff0c;支持多种GUI库。本文将为您介绍Python3 GUI的一些基本概念…

web安全第一天 ,域名,dns

第一天 什么是域名&#xff1f;域名就是网络地址 在hhtp之后的就是域名 域名在哪里注册呢 国内注册商有很多&#xff0c;在网络上搜索一下阿里云万网就可以注册 什么是二级域名和多级域名 域名通常都是www.开头 &#xff0c;而www.被称为顶级域名&#xff0c;在搜索的时候…

【云计算与虚拟化】第五章 实验一 vCenter Server的高级功能—vMotion、虚拟内存、虚拟CPU、磁盘分配及资源池

实验一 vCenter Server的高级功能—vMotion、虚拟内存、虚拟CPU、磁盘分配及资源池 1.通过vSphere client 客户端登陆vCenter服务器 &#xff08;1&#xff09;再次新建一个共享存储&#xff0c;iSCSI类型的&#xff0c;大小60G&#xff0c;名称为iSCSI-2。 &#xff08;2…

C++模板类与函数

目录 分类 一、普通函数&#xff0c;参数和返回值是模板类的实例化版本&#xff08;只支持AA模板类的某一个具体化类&#xff09; 1&#xff09;代码 2&#xff09;分析 3&#xff09;运行结果 二、函数模板&#xff0c;参数和返回值是某种模板类&#xff08;只支持AA模板…

chatgpt赋能Python-python31个关键词

Python 31个关键词: 了解Python编程的基础知识 Python是一种高级编程语言&#xff0c;首次推出于1991年。自那时以来&#xff0c;Python已经成为最受欢迎的编程语言之一&#xff0c;它具有易读性、易维护性和清晰的语法。在这篇文章中&#xff0c;我们将介绍Python的31个关键词…

python3.8 安装 ssl 模块 和 _ctypes 模块

文章目录 前情提要安装 openssl-1.1.1重新编译安装 python3.8-rpath 编译选项介绍python3.8 跟 python3.10 的区别 前情提要 在我成功给 python3.10 安装 ssl 模块后自以为是的写下了 “虽然我编译的是 python3.10&#xff0c;但是对于 python3.6、python3.8 应该同样适用。”…

chatgpt赋能Python-python3_0怎么安装

Python3.0怎么安装 Python3.0是一个优秀的编程语言&#xff0c;其简单易学&#xff0c;易用性强&#xff0c;且具有强大的功能和广泛的应用。如果您需要在您的电脑上使用Python3.0&#xff0c;本文将为您介绍如何进行安装。 下载Python3.0 首先&#xff0c;您需要从官方网站…

教育不得不改变,ChatGPT可以提供个性化学习

近日&#xff0c;OpenAI的首席执行官Sam Altman接受ABC新闻首席商业、技术和经济记者Rebecca Jarvis的独家采访。Sam Altman提到了对错误信息的担忧、探讨了人工智能与人类工作的关系、ChatGPT与教育的关系等。 ChatGPT今年1月面世后&#xff0c;在短短几个月内就达到了1亿月活…

代码随想录训练营Day43| 1049. 最后一块石头的重量 II 494. 目标和 474.一和零

目录 学习目标 学习内容 1049. 最后一块石头的重量 II 494. 目标和 474.一和零 学习目标 1049. 最后一块石头的重量 II 494. 目标和 474.一和零 学习内容 1049. 最后一块石头的重量 II 1049. 最后一块石头的重量 II - 力扣&#xff08;LeetCode&#xff09;https://l…

【云计算与虚拟化】第三章 实验二 在ESXi主机中安装操作系统

实验二 在ESXi主机中安装操作系统 为ESXi主机添加一块数据存储磁盘&#xff0c;大小为80G&#xff0c;磁盘名称为&#xff1a;ESXi-loc-80G&#xff08;截图&#xff09; 2.登录vSphere Client&#xff0c;加载新添加的数据存储磁盘&#xff0c;加载大小为60G&#xff0c;并将…

网络编程——嵌入式(驱动)软开基础(五)

1. 列举一下OSI协议的各种分层。说说你最熟悉的一层协议的功能。 (1)七层划分为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。 (2)五层划分为:应用层、传输层、网络层、数据链路层、物理层。 (3)四层划分为:应用层、传输层、网络层、网络接口层。(…

springboot+java+jsp校园二手书旧书交易交换系统

前台功能&#xff1a;用户进入系统可以对首页、书籍信息、校园公告、个人中心、后台管理等功能进行操作&#xff1b; 后台主要是管理员&#xff0c;管理员功能包括主页、个人中心、学生管理、发布人管理、书籍分类管理、书籍信息管理、交易信息管理、交换信息管理、系统管理等&…

Linux运维常用grep命令使用

grep 命令是 Linux 运维中非常常见的文本搜索工具 常用的使用方法如下&#xff1a; 1、查找包含指定字符串的行 grep "pattern" file.txt 其中&#xff0c;pattern 是要查找的字符串&#xff0c;file.txt 是要搜索的文件名。 2、查找不包含指定字符串的行 grep -v…

chatgpt赋能Python-python3_9_2怎么换行

Python 3.9.2 换行方法 Python 3.9.2 已经出现了&#xff0c;除了一些修复和新功能&#xff0c;Python 3.9.2 还包括了一些在换行方面的改进。在本文中&#xff0c;我们将介绍 Python 3.9.2 中换行的新方法。 换行符 在 Python 3.9.2 中&#xff0c;我们可以使用换行符来实现…