
一、概念
流式布局能实时响应屏幕可用空间进行重排界面,当一行(或一列)放不下里面的内容时会自动换行,还允许元素使用权重(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() }
}
}
}