Compose 组件 - 流式布局 FlowLayout(FlowColumn、FlowRow)

一、概念

流式布局能实时响应屏幕可用空间进行重排界面,当一行(或一列)放不下里面的内容时会自动换行,还允许元素使用权重(Modifier.weight)进行动态调整大小,以将项目分配到容器中。

|------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| FlowRow | @Composable fun FlowRow( modifier: Modifier = Modifier, horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, verticalArrangement: Arrangement.Vertical = Arrangement.Top, itemVerticalAlignment: Alignment.Vertical = Alignment.Top, maxItemsInEachRow: Int = Int.MAX_VALUE, //单行元素数量 maxLines: Int = Int.MAX_VALUE, //行数 content: @Composable FlowRowScope.() -> Unit, ) |
| FlowColumn | @Composable fun FlowColumn( modifier: Modifier = Modifier, verticalArrangement: Arrangement.Vertical = Arrangement.Top, horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, itemHorizontalAlignment: Alignment.Horizontal = Alignment.Start, maxItemsInEachColumn: Int = Int.MAX_VALUE, //单列元素数量 maxLines: Int = Int.MAX_VALUE, //列数 content: @Composable FlowColumnScope.() -> Unit, ) |

二、简单使用

Kotlin 复制代码
@Composable
fun Filters() {
    val filters = listOf("Washer/Dryer", "Ramp access", "Garden", "Cats OK", "Dogs OK", "Smoke-free")
    FlowRow(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
        filters.forEach { title ->
            var selected by remember { mutableStateOf(false) }
            val leadingIcon: @Composable () -> Unit = { Icon(Icons.Default.Check, null) }
            FilterChip(
                selected = selected,
                onClick = { selected = !selected },
                label = { Text(title) },
                leadingIcon = if (selected) leadingIcon else null
            )
        }
    }
}

三、进阶使用

Kotlin 复制代码
@Composable
fun AdapterScreen(
    windowWidthSizeClass: WindowWidthSizeClass = currentWindowAdaptiveInfo().windowSizeClass.windowWidthSizeClass
) {
    FlowRow(
        modifier = Modifier.fillMaxSize(),
        horizontalArrangement = Arrangement.Center,
        verticalArrangement = Arrangement.Center,
        maxItemsInEachRow = 3   //可用空间再大一行也只有3个子元素
    ) {
        //大型组件作为第一个直接放
        Big()
        //对紧凑型来说,小型组件横着放,放不下会自动换行(效果图横着显示2个)
        //对中等型和展开型来说,用容器 FlowColumn 包裹小型组件使之成为一个整体
        //充分利用纵向空间,在容器中竖着放,放不下会自动换列(效果图竖着显示两个)
        if(windowWidthSizeClass == WindowWidthSizeClass.COMPACT) {
            Small()
            Small()
        } else {
            FlowColumn {
                Small()
                Small()
            }
        }
        //中型组件同理小型组件
        if(windowWidthSizeClass == WindowWidthSizeClass.COMPACT) {
            Medium()
            Medium()
        } else {
            FlowColumn {
                Medium()
                Medium()
            }
        }
    }
}

3.1 保持子元素内部动画状态 movableContentOf()

当流式布局进行重排的时候,子元素的动画会被打断(重新启动)。借助可移动内容 (Movable content)可以在子元素被移动的时候不丢失动画状态。

|--------------------|------------------------------------------------------------------------------------------|
| movableContentOf() | public fun movableContentOf( content: @Composable () -> Unit ): @Composable () -> Unit |

Kotlin 复制代码
val smallContent = remember {
    movableContentOf {
        Small()
        Small()
    }
}

FlowRow {
    if (windowSizeClass == WindowWidthSizeClass.Compact) {
        smallContent()
    } else {
        FlowColumn { smallContent() }
    }
}

3.2 子元素位移动画

当流式布局进行重排的时候,子元素是瞬间移动的,没有流畅自然的感觉。最外层使用 LookaheadScope 包裹能够让 Compose 在布局变化时执行中间测量过程,并告知子元素这些中间状态。使用 Modifier.animateBounds() 构建一个自定义的 Modifier 传递给子元素,构建时可以指定 boundsTransform 参数到自定义的 spring 规范从而定制动画的运行方式。

Kotlin 复制代码
//自定义动画
val boundsTransform = { _: Rect, _: Rect->
    spring(
        dampingRatio = Spring.DampingRatioNoBouncy,
        stiffness = Spring.StiffnessMedium,
        visibilityThreshold = Rect.VisibilityThreshold
    )
}

//最外层包裹一下
LookaheadScope {
    //单独创建 Modifier 方便多次传参给子元素
    val MyModifier = Modifier.animateBounds(
        lookaheadScope = this@LookaheadScope,
        boundsTransform = boundsTransform
    )
    val smallContent = remember {
        movableContentOf {
            small(modifier = MyModifier)    //将自定义的 Modifier 传递给子元素
            small(modifier = MyModifier)
        }
    }
    FlowRow {
        if (windowSizeClass == WindowWidthSizeClass.Compact) {
            smallContent()
        } else {
            FlowColumn { smallContent() }
        }
    }
}
相关推荐
峥嵘life2 小时前
Android 蓝牙设备连接广播详解-2026
android·python·学习
MusingByte5 小时前
别再裸用 Claude Code 了!安卓开发者必装 13 个官方推荐插件,效率翻 3 倍省 70% token
android
_李小白5 小时前
【android opencv学习笔记】Day 29: 滤波算法之Sobel 边缘检测
android·opencv·学习
Dxy12393102166 小时前
Python 操作 MySQL 事务:从入门到避坑
android·python·mysql
峥嵘life7 小时前
Android getprop 属性限制详解:User 版本属性获取问题分析
android·开发语言·python·学习
一航jason8 小时前
Speed Tools:一套低侵入的 Android 插件化 + 动态换肤 + 字体切换框架
android·插件化·组件化·换肤
李斯维9 小时前
Jetpack 可观察数据容器 LiveData 的入门与基础使用
android·android jetpack
问心无愧051310 小时前
ctf show web入门261
android·前端·笔记
alexhilton10 小时前
车载系统中的可扩展UI:从UI嵌入到系统窗口编排
android·kotlin·android jetpack
Cloud_Shy61810 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第一章 Item 4 - 6)
android·数据库·论文阅读·python