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() }
        }
    }
}
相关推荐
帅次1 小时前
Navigation Compose:NavHost、NavController 与参数
android·kotlin·gradle·android jetpack·compose
程序员陆业聪2 小时前
架构哲学与工程化:从开发体验到CI/CD的全维度对比|跨平台框架深度对决(三)
android
程序员陆业聪2 小时前
Android网络全链路拆解:一次HTTP请求背后的性能陷阱
android
程序员陆业聪2 小时前
渲染引擎与性能拆解:自绘vs原生渲染vs Bridge的终极对决|跨平台框架深度对决②
android
程序员陆业聪9 小时前
技术选型决策树:什么团队、什么项目该选什么框架 | 跨平台框架深度对决(4)
android
星辰徐哥11 小时前
Rust异步测试与调试的实践指南
android·java·rust
星河耀银海11 小时前
C++ 运算符重载:自定义类型的运算扩展
android·java·c++
阿巴斯甜11 小时前
Activity 之间大量数据传递有哪些方案?
android
阿巴斯甜11 小时前
必看1
android