Jetpack Compose LazyGrid使用全解
前言
前段时间Compose发布了1.2.0
beta版本,最大的变化之一莫过于LazyLayout
去除了实验性标志。所以接下来,咱们不妨一起看看LazyGrid
的用法 (嗯?这和上一句有关系吗)
LazyGrid
包含两种微件:LazyVerticalGrid
和LazyHorizontalGrid
。两者内部均由LazyLayout
实现(包括LazyColumn
和LazyRow
也是由LazyLayout
实现的)。不过今天我们不去考虑底层的LazyLayout
,单纯着眼于Grid们
为行文方便,此处仅以LazyVerticalGrid
为例。
基本使用
最简单的使用如下所示:
@Composable
fun SimpleLazyGrid(){
LazyVerticalGrid(
modifier = Modifier.fillMaxWidth(),
// 固定两列
columns = GridCells.Fixed(2) ,
content = {
items(12){
RandomColorBox(modifier = Modifier.height(200.dp))
}
}
)
}
其中用到的RandomColorBox
仅仅是Box加上随机颜色的背景,唯一注意的是对于LazyLayout,因为涉及到重组过程,所以如果需要记住这个Color(重组时颜色不变),则需要使用rememberSaveable
,其余不再赘述
上面的效果如下
简单的网格布局就实现了
添加间隙
要为子元素之间添加空隙也很简单,指定一下arrangemnt
为spacedBy
即可
//...
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
效果如下
当然也可以添加整体的外边距,设置contentPadding = PaddingValues()
即可,如下:
contentPadding = PaddingValues(12.dp)
适应大小
上述情况实际上会根据最大宽度来调整,在横屏状态下就可能会惨不忍睹(比如你加载有图片的情况)
所以除了固定列数外,还可以固定宽度,由Compose自动确定要放几列。这个也很简单,就是设置columns
参数为Adaptive
即可
// 固定宽度,自适应列数
columns = GridCells.Adaptive(200.dp) ,
效果如下:
横屏
竖屏
可以看到,我们指定的200.dp
是最小值,由于能够容纳一个又无法容纳两个,Compose为我们自动调整为了只放一个,占满全部剩余宽度。
异形与自定义
某些元素占满全部宽度
item
和items
均有span
参数,设置此参数即可设定当前元素会占据几格
对于下面的代码:
// 固定列数
columns = GridCells.Fixed(3) ,
content = {
item(span = {
// 占据最大宽度
GridItemSpan(maxLineSpan)
}){
RandomColorBox(modifier = Modifier.height(50.dp))
}
items(12){
RandomColorBox(modifier = Modifier.height(200.dp))
}
},
最上面那个元素就会占据一行,如下:
上面用到的maxLineSpan
即为当前行的最大Span,除此之外,还有另一个值maxCurrentLineSpan
,二者之间关系如下
更复杂的自定义
columns
其实可以自定义,比方说,我们需要让一行中三个元素,宽度分别为1:2:1,那其实可以这样写。具体细节请参考下面的源码,返回的值即为各元素的宽度组成的List
// 自定义实现1:2:1
columns = object : GridCells {
override fun Density.calculateCrossAxisCellSizes(
availableSize: Int,
spacing: Int
): List<Int> {
// 总共三个元素,所以其实两个间隔
// |元素|间隔|元素|间隔|元素|
// 计算一下所有元素占据的空间
val availableSizeWithoutSpacing = availableSize - 2 * spacing
// 小的两个大小即为剩余空间(总空间-间隔)/4
val smallSize = availableSizeWithoutSpacing / 4
// 大的那个就是除以2呗
val largeSize = availableSizeWithoutSpacing / 2
return listOf(smallSize, largeSize, smallSize)
}
}
效果如下
其余的效果大家就发挥想象啦
如果你想对排列方式也自定义,可以自己实现Arrangement.Vertical
,视频中有给出例子(18:51左右)。这里感觉用处不大,不赘述了
一些提示 For LazyLayout
-
不要设置大小为0的控件
这类问题主要在异步加载的场景中,可能加载之前你会将原本的大小设置为0(就是什么也没有)。在这种情况下,Compose在初始时将测量所有内容(因为他们高度为0,所以都在屏幕内),之后当数据加载完后,Compose又会重新重组。
相反,你应当尽量保证数据加载前后item整体大小不变(如手动设置高度、使用placeholder等),以帮助LazyLayout正确计算哪些会被显示在屏幕上
-
避免嵌套同方向的可滚动微件
避免使用如下代码(其实你这么用会直接报错)
Column(modifier = Modifier.verticalScroll()) { LazyColumn(/*这里不设置高度*/) }
而应当改为:
LazyColumn { item { Header() } items(){ } item{ Footer() } }
-
谨慎将多个子微件放到同一
item
中即谨慎写出类似下面的代码
LazyColumn { item { // 两个微件放在同一item里 RandomColorBox(modifier = Modifier.size(40.dp)) RandomColorBox(modifier = Modifier.size(40.dp)) } }
在这种情况下,Compose尽管可以按顺序渲染出这些子微件,但同一个
item
下的微件会被当作一个整体。如果某一部分可见,则其与部分也会被一起重组和绘制,可能会影响性能。在最严重情况下,整个LazyColumn
仅包含一个item
,那就完全失去了Lazy
的特性。另一个问题是对于scrollToItem()
这类方法,它们的index在计算时是按item
而不是所有内部子元素排列的,也就是说,对于下面的例子,尽管总共有4个微件,但算index的时候只有0/1/2三个而已。
不过也有些情况倒是推荐这么用,比如在item
中包含微件本身和Divider
。一是二者本身语义上就相关联,Divider也不该影响index;二是Divider较小,不影响性能
- 使用Type
如果你的列表项有多种不同的类型,可以在item
或items
方法中指定contentType
,这有助于Compose在重组时选择相同Type的微件进行复用,可以提高性能。
contentType = { data.type }
Google画的饼
参考的视频中Google其实画了些饼
- 瀑布流布局正在开发中
- item添加和删除的动画也正在开发中
目前RecyclerView
对应的瀑布流布局Compose
中还没有对应实现,我试图用VerticalLazyGrid
实现然并不行,它摆放的时候会确保每行高度一样……目前的开源库都是多个LazyColumn
并排实现的伪效果。所以还是等吧
更多推荐
所有评论(0)