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() }
        }
    }
}
相关推荐
树下困觉眯2 小时前
View绘制流程学习--获取WindowManager的三种方式
android·系统架构·view design
vilya3 小时前
我怎么给手机 GUI Agent 做双通道感知:无障碍树为主,投屏像素兜底
android·人工智能
李子红了时4 小时前
阿狸舞台APP(安卓端手机EncFS解密+压缩包解压+视频播放)
android·音视频
事圆则缓4 小时前
Android 使用 Jenkins 实现 CI/CD,并用 SonarQube 建立质量门禁
android·ci/cd·jenkins
其实防守也摸鱼4 小时前
DeepSeek Harness 开源贡献手记:从 Issue 到 Merge 的完整旅程
android·数据库·学习·ai·oracle·自动化
恋猫de小郭4 小时前
Android CLI 支持 AI Agent 通过 Device Streaming 调试云真机
android·前端·flutter
素师良码1 天前
第5篇:显示驱动必备调试工具浅析
android
维克兜率天1 天前
【维克】配对交易的季节性:哪些品种适合长拿?
android·开发语言·笔记·python·算法·kotlin·量化
墨天梦1 天前
B06_XML控件布局与ViewBinding
android·kotlin
晚风叙码1 天前
MySQL 表的操作:从建表到删表,一篇讲清楚
android·mysql·oracle