Compose | UI组件(十二) | Lazy Layout - 列表

本文详细介绍了JetpackCompose中的LazyListScope、LazyColumn和LazyRow组件,涉及内容包括作用域、基本使用、边距设置、rememberLazyListState的利用以及items(key)对性能的影响。
摘要由CSDN通过智能技术生成


前言

现在应用市场上很多产品都少不了列表展示需求场景,例如通讯录,短信,音乐列表等等。

所以本篇文章讲解的组件 - 列表LazyListLazyRow


在了解 LazyListLazyRow 之前,我们先了解下 LazyListScope作用域

LazyListScope作用域 用来干什么?

LazyColumnLazyRow 内部都是继承 LazyList组件 实现,但 LazyList 不能直接使用

LazyListcontent 是一个 LazyListScope.() -> Unit 类型的作用域
LazyListScope 提供了 item , items(Int) , item(List) , itemsIndexed(List) 扩展函数来展示列表内容

item:展示单项数据
items(Int):展示多项整型数据
items(List) 展示一组集合数据
itemsIndexed(List) 展示一组集合数据,并且带有下标

val list = ('A'..'Z').map { it.toString() }
LazyColumn{
    item { Text(text = "first item") }
    
    items(10){ index ->
        Text(text = "$index")
    }

    item { Text(text = "last item") }

    items(list){  item ->
        Text(text = item)
    }

    itemsIndexed(list){ index, item ->
        Text(text = "$index/$item")
    }
}

LazyColumn组件含义?

LazyColumn 就是一个纵向滚动列表,用来显示一组纵向数据,并且可以滑动列表

@Composable
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(), //fling行为处理逻辑
    userScrollEnabled: Boolean = true,                                 //是否允许滑动
    content: LazyListScope.() -> Unit                                  //LazyList作用域
)

LazyColumn的基本使用

LazyColumn 组件 相比传统的 RecyclerView 少写很多代码
RecyclerView 需要在xml中声明一个RecyclerView控件,再在xml中声明一个子控件,再创建一个适配器Adapter,最后在activity中 指定 RecyclerView 的布局类型,再为其填充数据
LazyColumn 就很简单了,看如下代码的实现,就知道了

@Composable
fun LazyColumnList() {
    LazyColumn {
        items(20) { i ->
            Text(
                text = "Item $i",
                modifier = Modifier
                    .fillMaxWidth()
                    .height(60.dp)
            )
        }
    }
}

LazyColumn Padding设置边距

Padding可以设置列表边距,但是会出现切割现象,我们来看下代码

    val list = ('A'..'Z').map { it.toString() }
    LazyColumn {
        itemsIndexed(list) { index, letter ->
            Card(
                modifier = Modifier
                    .width(120.dp)
                    .height(200.dp)
                    .padding(10.dp)
            ) {
                Text(
                    text = "$index $letter",
                    textAlign = TextAlign.Center,
                    fontSize = 20.sp,
                    modifier = Modifier
                        .fillMaxSize()
                        .wrapContentHeight(Alignment.CenterVertically)
                )
            }
        }
    }

现在看下运行代码的效果:
在这里插入图片描述

注:
从上图可以看出来最后一个出现了被切割现象,那如何解决这个问题呢?

那就要用到 LazyColumn 提供的 contentPadding 解决

LazyColumn 设置边距 (contentPadding)

LazyColumn 设置边距用的是contentPadding,能保证上下两边的边距相等同时,还不会在滚动的时候,出现切割现象

val list = ('A'..'Z').map { it.toString() }
LazyColumn(contentPadding = PaddingValues(top = 40.dp, start = 10.dp, bottom = 40.dp, end = 10.dp)) {
     itemsIndexed(list) { index, letter ->
         Card(
             modifier = Modifier
                 .fillMaxWidth()
                 .height(120.dp)
//                    .padding(10.dp)
         ) {
             Text(
                 text = "$index $letter",
                 textAlign = TextAlign.Center,
                 fontSize = 20.sp,
                 modifier = Modifier.fillMaxSize().wrapContentHeight(Alignment.CenterVertically)
             )
         }
     }
 }

现在再看下效果图:
在这里插入图片描述

注:
现在可以看出来,没有切割的现象了,但有个问题,中间没有了间距
Lazy Layout提供了专门给子项之间设置边距的属性,使用Arrangement.spacedBy()即可

LazyColumn 为每个子项设置边距 (Arrangement.spacedBy())

Arrangement.spacedBy() 专门为子项设置边距

val list = ('A'..'Z').map { it.toString() }
LazyColumn(contentPadding = PaddingValues(top = 40.dp, start = 10.dp, bottom = 40.dp, end = 10.dp),
           verticalArrangement = Arrangement.spacedBy(20.dp)) {
		    itemsIndexed(list) { index, letter ->
		        Card(
		            modifier = Modifier
		                .fillMaxWidth()
		                .height(120.dp)
		        ) {
		            Text(
		                text = "$index $letter",
		                textAlign = TextAlign.Center,
		                fontSize = 20.sp,
		                modifier = Modifier.fillMaxSize()
		                                   .wrapContentHeight(Alignment.CenterVertically)
		            )
		        }
    }
}

效果图如下:
在这里插入图片描述

LazyColumn 根据 rememberLazyListState 记录item位置

有时候有这样的需求:让组件随着列表的滚动进行一些额外的响应。如随着滚动隐藏和显示某个组件
这时候 rememberLazyListState 就排上用场了

以下是rememberLazyListState 源码:

@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,        //第一个可见子项元素的下标
    initialFirstVisibleItemScrollOffset: Int = 0  //第一个可见子项元素的偏移距离
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(
            initialFirstVisibleItemIndex,
            initialFirstVisibleItemScrollOffset
        )
    }
}

根据列表第一项可见显示组件,不可见隐藏组件

@SuppressLint("FrequentlyChangedStateReadInComposition")
@Composable
fun ListLayout() {
    val state = rememberLazyListState()
    Box {
        val list = ('A'..'Z').map { it.toString() }
        LazyColumn(state = state,
            contentPadding = PaddingValues(top = 40.dp, start = 10.dp, bottom = 40.dp, end = 10.dp),
            verticalArrangement = Arrangement.spacedBy(20.dp)) {
            itemsIndexed(list) { index, letter ->
                Card(
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(120.dp)
                ) {
                    Text(
                        text = "$index $letter",
                        textAlign = TextAlign.Center,
                        fontSize = 20.sp,
                        modifier = Modifier
                            .fillMaxSize()
                            .wrapContentHeight(Alignment.CenterVertically)
                    )
                }
            }
        }

        if (state.firstVisibleItemIndex  == 0) {
            FloatingActionButton(
                onClick = {},
                shape = CircleShape,
                modifier = Modifier
                    .align(Alignment.CenterEnd)
                    .padding(20.dp)
            ) {
                Icon(Icons.Filled.Star, "Add Button")
            }
        }
    }
}

在这里插入图片描述

根据 items函数 新增 一个key参数,增加和删除操作时,提高页面性能问题

因为compose中往列表添加一项数据,就会整体往后移,数据越多,页面性能越差
所以Google给出了一个解决方案:在items函数添加一个唯一标识的key

@Composable
fun SubVerticalScrollable() {
    val list = ('A'..'Z').map { it.toString() }
    LazyColumn(modifier = Modifier.height(300.dp)) {
        items(list, key = { it }) { letter ->
            ...
        }
    }
}

LazyRow的含义

LazyRow 和 LazyColumn用法基本相同,唯一不同的是横向布局的列表组件

@Composable
fun LazyRow(
    modifier: Modifier = Modifier,                                     //修饰符
    state: LazyListState = rememberLazyListState(),                    //记录列表位置状态
    contentPadding: PaddingValues = PaddingValues(0.dp),               //整体内容周围的一个边距
    reverseLayout: Boolean = false,                                    //是否反转列表
    horizontalArrangement: Arrangement.Horizontal =
        if (!reverseLayout) Arrangement.Start else Arrangement.End,    //子组件横向对齐方式
    verticalAlignment: Alignment.Vertical = Alignment.Top,             //子组件纵向对齐方式
    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), //fling行为处理逻辑
    userScrollEnabled: Boolean = true,                                 //是否允许滑动
    content: LazyListScope.() -> Unit                                  //LazyList作用域
)

LazyRow的使用

val list = ('A'..'Z').map { it.toString() }
LazyRow(state = state,
	   contentPadding = PaddingValues(top = 40.dp, start = 10.dp, bottom = 40.dp, end = 10.dp),
	   horizontalArrangement = Arrangement.spacedBy(20.dp)) {
	   itemsIndexed(list) { index, letter ->
	       Card(
	           modifier = Modifier
	               .height(300.dp)
	               .width(120.dp)
	       ) {
	           Text(
	               text = "$index $letter",
	               textAlign = TextAlign.Center,
	               fontSize = 20.sp,
	               modifier = Modifier
	                   .fillMaxSize()
	                   .wrapContentHeight(Alignment.CenterVertically)
	           )
	       }
	   }
}

到这里,基本上Lazy Layout 基本上覆盖到了,还有什么问题,欢迎反馈和评论区留言


总结

  1. LazyListScope作用域提供了 item , items(Int) , item(List) , itemsIndexed(List) 扩展函数来展示列表内容
  2. LazyColumn 就是一个纵向滚动列表
  3. contentPadding 设置列表组件的整个内容的边距
  4. Arrangement.spacedBy() 为列表的每个子项设置边距
  5. LazyColumn 根据 rememberLazyListState 记录第一个可见子项元素位置
  6. items函数 新增 一个key参数,设置唯一性,增加和删除操作时,性能得到优化和提高
  7. LazyRow 是一个横向布局的列表组件,用法和 LazyColumn一致
  • 21
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个使用Compose实现的可拖拽排序的完整示例代码: ```kotlin import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import kotlin.random.Random data class Item(val id: Int, val text: String) @Composable fun DraggableLazyColumn(items: List<Item>, onItemsChanged: (List<Item>) -> Unit) { var draggedItem: Item? by remember { mutableStateOf(null) } var dragIndex by remember { mutableStateOf(-1) } LazyColumn { itemsIndexed(items) { index, item -> if (dragIndex == index) { // This item is being dragged, so don't show it Spacer(modifier = Modifier .height(64.dp) .fillMaxWidth()) } else { ListItem( text = { Text(item.text) }, modifier = Modifier .draggable( state = rememberDraggableState { delta -> val newIndex = calculateNewIndex(dragIndex, delta, items.size) if (newIndex != dragIndex) { // Update the list when the item is dragged to a new position onItemsChanged(items.move(dragIndex, newIndex)) dragIndex = newIndex } }, // When the item is picked up, remember it and its index onDragStarted = { draggedItem = item dragIndex = index }, // When the drag is finished, clear the draggedItem and dragIndex onDragStopped = { draggedItem = null dragIndex = -1 } ) // Set the background color to be different for the dragged item .background(if (dragIndex == index) Color.LightGray else Color.White) ) } } } } @Composable fun App() { var items by remember { mutableStateOf(generateItems(10)) } Scaffold( topBar = { TopAppBar(title = { Text("Draggable Lazy Column") }) } ) { Column( modifier = Modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { DraggableLazyColumn(items = items) { newItems -> items = newItems } } } } fun main() = Window { App() } fun generateItems(n: Int): List<Item> { return List(n) { Item(it, "Item ${it + 1}") } } fun calculateNewIndex(index: Int, delta: Float, size: Int): Int { // Calculate the new index based on the y delta and the number of items val newIndex = (index + (delta / 64.dp.toPx())).toInt() return newIndex.coerceIn(0, size - 1) } fun <T> List<T>.move(fromIndex: Int, toIndex: Int): List<T> { val mutableList = this.toMutableList() val item = mutableList.removeAt(fromIndex) mutableList.add(toIndex, item) return mutableList } ``` 在这个示例中,我们定义了一个 `DraggableLazyColumn` 组件,它接受一个 `List<Item>` 和一个回调函数 `onItemsChanged`,当列表项被拖拽排序时,我们将调用此回调函数以更新列表项的顺序。 我们使用 `LazyColumn` 来渲染列表,并使用 `itemsIndexed` 将每个项目与其索引一起传递。对于正在拖动的项目,我们使用一个 `Spacer` 来占用列表中的空间,以便其他项目能够正确放置在其位置上。 对于所有其他项目,我们使用 `ListItem` 来显示每个项目的文本,并在其上使用 `Modifier.draggable()` 使其可拖动。我们使用 `onDragStarted` 和 `onDragStopped` 回调来跟踪正在拖动的项目以及其索引,并在 `onDragStarted` 中设置拖动项目的背景颜色。在 `state` 回调中,我们计算出新的索引,并在 `onDragStopped` 中清除拖动项目和索引。 最后,我们使用 `generateItems` 函数生成一些随机项目,并将它们传递给 `DraggableLazyColumn` 组件
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谭祖爱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值