《Jetpack Compose系列学习》-15 Compose中的横向列表LazyRow和网格列表LazyVerticalGrid

1,337 阅读3分钟

横向列表LazyRow

我们知道,Android View中的RecyclerView,可以通过设置其LayoutManager的方向来实现横向列表。Compose中用LazyRow来实现。

val dataList = arrayListOf<Int>()
for (index in 0 .. 10) { // 数据源
    dataList.add(index)
}
LazyRow { // 横向列表
    items(dataList) { data ->
        Text(text = "$data")
    }
}

image.png

能看到0-10的数字横向展示。从代码上看,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的逻辑
    content: LazyListScope.() -> Unit // 描述内容的代码块
) {
    // 省略...
}

可以看到,除了horizontalArrangement和verticalAlignment参数外,别的参数基本一致。

默认情况下,每个列表项的状态均与该项在列表中的位置相对应。但是数据集如果发生变化,可能会导致问题出现,因为位置发生变化的列表项实际上会丢失所有记忆状态。比如LazyColumn中的LazyRow,如果某个LazyRow更改的项位置,用户将丢失在该行内的滚动位置。为了避免这种情况,可以为每个列表项提供稳定的唯一键,为key参数提供一个块,提供稳定的键可以使项状态在数据集发生更改后还保持一致。我们看个例子:

val dataList = arrayListOf<Int>()
for (index in 0 .. 10) {
    dataList.add(index)
}
LazyRow {
    items(
        items = dataList, 
        key = { index ->
            index
        }
    ) { data ->
        Text(text = "$data")
    }
}

我们在之前的例子中添加了项键key,类型是Any,所以我们可以传任何值,但需要注意的是,我们提供的参数类型必须是Bundle支持的类型。

网格列表LazyVerticalGrid

我们打开手机相册或者朋友圈里的图片,都能看到是以网格的列表形式展示。在Android View中的RecyclerView展示网格布局也是设置LayoutManager为GridLayoutManger。在Compose中用LazyVerticalGrid实现这种效果。

val photos = arrayListOf<Int>()
for(index in 0 .. 20) { // 添加21张图片
    photos.add(R.drawable.ic_launcher_background)
}

LazyVerticalGrid(
    cells = GridCells.Adaptive(minSize = 60.dp)) {
    items(photos) { photo ->
        Image(
            painter = painterResource(id = photo),
            contentDescription = "",
            modifier = Modifier.padding(2.dp)
        )
    }
}

image.png 我们可以看到上图的网格列表效果。

我们看看它的源码:

@ExperimentalFoundationApi
@Composable
fun LazyVerticalGrid(
    cells: GridCells, // 描述单元格如何形成列
    modifier: Modifier = Modifier, // 描述符
    state: LazyListState = rememberLazyListState(), // 它和LazyRow一致
    contentPadding: PaddingValues = PaddingValues(0.dp), // 内边距
    content: LazyGridScope.() -> Unit // 内容代码块
) {
    // 省略...
}

我们可以看到有注解ExperimentalFoundationApi修饰,表示是实验性的API,我们发现里面有两个陌生的参数,第一个是cells和最后一个LazyGridScope。

描述单元格如何形成列————cells

cells类型是GridCells,用来描述单元格如何形成列,而且这个参数没有默认值,也不能为空,看看它的源码:

@ExperimentalFoundationApi
sealed class GridCells {
    
    @ExperimentalFoundationApi
    class Fixed(val count: Int) : GridCells()

    @ExperimentalFoundationApi
    class Adaptive(val minSize: Dp) : GridCells()
}

它是一个密封类,而且有两个子类:Fixed和Adaptive。从这两个子类命名大致知道他们的意义,Fixed是固定的列数;Adaptive是设置最小宽度并进行自适应布局。先卡看Fixed的用法,它有一个int类型的参数,可以生成具有固定数量行或列的单元格。

val photos = arrayListOf<Int>()
for(index in 0 .. 20) { // 添加21张图片
    photos.add(R.drawable.ic_launcher_background)
}

LazyVerticalGrid(
    cells = GridCells.Fixed(count = 5)) { // 5列
    items(photos) { photo ->
        Image(
            painter = painterResource(id = photo),
            contentDescription = "",
            modifier = Modifier.padding(2.dp)
        )
    }
}

image.png

可以看到实际展示的效果就是5列展示的。Adaptive就是最开始例子代码中设置的,可以生成具有适应性的行数或列数的单元格,它尝试在每个单元格至少具有minSize空间和所有额外空间均匀分布的条件下,尽可能多地定位行或列。

LazyGridScope

content参数表示单元格的内容,类型是LazyGridScope。之前介绍lazyColumn的时候说过,它不接收@Composable内容块参数,而是接收一个LazyGridScope块。LazyVerticalGrid也不接收@Composable内容块参数,而是提供了一个LazyGridScope块。该块也提供了DSL,允许应用程序描述列表项内容,之后LazyVerticalGrid就和LazyColumn一样负责按照布局和滚动位置的要求添加每个列表项的内容。先看看LazyGridScope的源码:

@ExperimentalFoundationApi
interface LazyGridScope {
    
    fun item(content: @Composable LazyItemScope.() -> Unit)

    fun items(count: Int, itemContent: @Composable LazyItemScope.(index: Int) -> Unit)
}

其实这么一看,LazyGridScope和LazyListScope一样,都是接口,而且连方法都一样,只是类的实现不同,再看看LazyGridScope的扩展函数有哪些:

@ExperimentalFoundationApi
inline fun <T> LazyGridScope.items(
    items: List<T>,
    crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit
) = items(items.size) {
    itemContent(items[it])
}

@ExperimentalFoundationApi
inline fun <T> LazyGridScope.itemsIndexed(
    items: List<T>,
    crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
) = items(items.size) {
    itemContent(it, items[it])
}

@ExperimentalFoundationApi
inline fun <T> LazyGridScope.items(
    items: Array<T>,
    crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit
) = items(items.size) {
    itemContent(items[it])
}

@ExperimentalFoundationApi
inline fun <T> LazyGridScope.itemsIndexed(
    items: Array<T>,
    crossinline itemContent: @Composable LazyItemScope.(index: Int, item: T) -> Unit
) = items(items.size) {
    itemContent(it, items[it])
}

LazyGridScope的扩展方法和LazyListScope一模一样,所以使用方法也一模一样。上面使用了List构建LazyVerticalGrid,我们看看Array的方式来构建一个LazyVerticalGrid。

LazyVerticalGrid(
    cells = GridCells.Fixed(count = 3)) {
    items(photos.toArray()) { photo ->
        Image(
            painter = painterResource(id = photo as Int),
            contentDescription = "",
            modifier = Modifier.padding(2.dp)
        )
    }
}

image.png

可以看到,列表变为了3列展示,Array也可以正常使用。是不是很简单。好了,今天学习到这。相关代码已上传github: github.com/Licarey/com…