Compose - 布局组合项
组合函数不指定布局默认是Box堆叠。一些概念参考View版的ContraintLayout。
一、纵向 Colum
| inline fun Column( modifier = Modifier, verticalArrangement = Arrangement.Top, //子元素纵向排列规则 horizontalAlignment = Alignment.Start, //子元素横向排列规则 content: @Composable ColumnScope.() -> Unit ) |
1.1 使Column具备滑动能力
val scrollState = rememberScrollState()
Column(Modifier.verticalScroll(scrollState)) {
repeat(20) {
Text(text = "aaaaaaa")
}
}
二、横向 Row
| inline fun Row( modifier: Modifier = Modifier, horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, verticalAlignment: Alignment.Vertical = Alignment.Top, content: @Composable RowScope.() -> Unit ) |
三、堆叠 Box
| inline fun Box( modifier: Modifier = Modifier, contentAlignment: Alignment = Alignment.TopStart, propagateMinConstraints: Boolean = false, content: @Composable BoxScope.() -> Unit ) |
| fun Box(modifier: Modifier) |
四、约束布局 ConstraintLayout
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
各种对齐或嵌套过深的时候使用。需要注意以下几点:
- 通过 createRefs( ) 或 createRefFor( ) 为子元素创建引用(相当于创建ID)。
- 通过 Modifier.constranAs( ) 将组合函数和引用绑定,在Lambda中指定约束条件。
- 约束条件通过 linkTo( ) 或其它有用的方法指定。
- parent 是现有的引用,可用于指定对 ConstraintLayout 本身的约束条件。
- 默认尽可能小的容纳完子元素(wrap_content),设置子元素位置(gravity)依据的是内容宽高,若要相对于屏幕位置,需要对 ConstraintLayout 使用 Modifier.fillMaxWidth() 或 fillMaxHeight() 或fillMAxSize()。
- 默认允许子元素超出屏幕,宽度要设置Dimension.preferredWrapContent。
@Composable
inline fun ConstraintLayout(
modifier: Modifier = Modifier,
optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
crossinline content: @Composable ConstraintLayoutScope.() -> Unit
)
fun Modifier.constrainAs(
ref: ConstrainedLayoutReference, //指定绑定的引用
//提供现有参数start、top、end、bottom表示当前组合函数的上下左右四边,调用linkTo()指定该边的约束
constrainBlock: ConstrainScope.() -> Unit //设置约束条件
)
fun linkTo(
//指定连接到哪里,parent指ConstraintLayout(如)
anchor: ConstraintLayoutBaseScope.***Anchor,
margin: Dp = 0.dp,
goneMargin: Dp = 0.dp
)

@Composable
fun MyConstraintLayout() {
ConstraintLayout {
//为每个子元素创建引用
val (buttonRef, textRef) = createRefs()
Button(
onClick = { /* Do something */ },
//将组合函数和引用关联
modifier = Modifier.constrainAs(buttonRef) {
//指定约束条件,现有引用parent是指ConstraintLayout
top.linkTo(parent.top, margin = 16.dp)
}
) {
Text("Button")
}
Text(
text = "Text",
Modifier.constrainAs(textRef) {
top.linkTo(buttonRef.bottom, margin = 16.dp)
//centerAround(button1.end) //自己中间对齐谁的哪边
centerHorizontallyTo(parent) //水平居中
}
)
}
}
4.1 栅栏 Barrier

@Composable
fun MyBarrier() {
ConstraintLayout {
val (button1, button2, text) = createRefs()
Button(onClick = { }, modifier = Modifier.constrainAs(button1) {
top.linkTo(parent.top, margin = 16.dp)
}) {
Text(text = "Button1")
}
Text(text = "Text", modifier = Modifier.constrainAs(text) {
top.linkTo(button1.bottom, margin = 16.dp)
centerAround(button1.end) //自己中间对齐谁的哪边
})
val barrier = createEndBarrier(button1, text) //将button1和text组合成一个Barrier
Button(onClick = { }, modifier = Modifier.constrainAs(button2) {
top.linkTo(parent.top, margin = 16.dp)
start.linkTo(barrier)
}) {
Text(text = "Button2")
}
}
}
4.2 基准线 Guideline
| createGuidelineFromTop(offset: Dp) createGuidelineFromStart(offset: Dp) createGuidelineFromEnd(offset: Dp) | 上下左右基于父布局的偏移量 |
| createGuidelineFromTop(fraction: Float) createGuidelineFromStart(fraction: Float) createGuidelineFromEnd(fraction: Float) | 上下左右基于父布局的百分比 |
| createGuidelineFromAbsoluteLeft(offset: Dp) createGuidelineFromAbsoluteLeft(fraction: Float) createGuidelineFromAbsoluteRight(offset: Dp) createGuidelineFromAbsoluteRight(fraction: Float) | 国际化 |
| Dimension.preferredWrapContent | 布局大小根据内容设置,并受布局约束影响。 |
| Dimension.wrapContent 默认值 | 布局大小只根据内容设置,不受布局约束影响。 |
| Dimension.fillToConstraints | 布局大小将展开填充由布局约束所限制的空间。 |
| Dimension.preferredValue | 布局大小是一个默认值,并受布局约束影响。 |
| Dimension.value | 布局大小是一个默认值,不受布局约束影响。 |
| Dimension.preferredWrapContent.adLast(100.dp) Dimension.preferredWrapContent.atMost(100.dp) | 可组合设置布局大小,设置最大/最小布局大小。 |

@Composable
fun MyGuideline() {
ConstraintLayout {
val text = createRef()
val guideline = createGuidelineFromStart(fraction = 0.5f)
Text(
text = "Hellow Wodr!Hellow Wodr!Hellow Wodr!Hellow Wodr!Hellow Wodr!Hellow Wodr!Hellow Wodr!",
modifier = Modifier.constrainAs(text) {
linkTo(start = guideline, end = parent.end)
//默认允许子元素超出屏幕,所以这里要设置一下
width = Dimension.preferredWrapContent
}
)
}
}
4.3 链 Chain
| fun createHorizontalChain( vararg elements: ConstrainedLayoutReference, //需要打包在一起的子元素引用 ) |
| fun createVerticalChain( vararg elements: ConstrainedLayoutReference, chainStyle: ChainStyle = ChainStyle.Spread ) |
| ChainStyle.Spread 默认 | 所有子元素平均分布在父布局中 |
| ChainStyle.SpreadInside | 头尾子元素分布在两端,其余平均分布剩下空间。 |
| ChainStyle.Packed | 所有子元素打包在一起,放在链条中间。 |

@Composable
fun MyChain() {
ConstraintLayout(modifier = Modifier.fillMaxSize()) {
val (box1, box2, box3) = createRefs()
createHorizontalChain(box1, box2, box3, chainStyle = ChainStyle.Spread)
Box(modifier = Modifier.size(100.dp).background(Color.Red).constrainAs(box1) {})
Box(modifier = Modifier.size(100.dp).background(Color.Green).constrainAs(box2) {})
Box(modifier = Modifier.size(100.dp).background(Color.Blue).constrainAs(box3) {})
}
}
4.4 约束集解耦 ConstraintSet
上面约束都是写在子元素里的,可以抽取出来在外部定义,通过传参的方式解耦,即动态约束。
- 创建 ConstraintSet(定义引用和对应的约束条件)传给 ConstraintLayout。
- 子元素通过 Modifier.layourId() 设置为 ConstraintSet 中对应的引用。

@Composable
fun Before() {
val margin = 16.dp
ConstraintLayout {
val (button, text) = createRefs()
Button(onClick = {}, modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = margin)
}) {
Text(text = "Button")
}
Text(text = "Text", modifier = Modifier.constrainAs(text) {
top.linkTo(button.bottom, margin = margin)
})
}
}
//外部定义约束集
private fun myContraints(margin: Dp) = ConstraintSet {
//创建引用
val button = createRefFor(id = "button")
val text = createRefFor(id = "text")
//配置约束条件
constrain(button) {
top.linkTo(parent.top, margin)
}
constrain(text) {
top.linkTo(button.bottom, margin)
}
}
@Composable
fun after() {
//该组合项提供的现有参数可用来根据可用空间调用不同的可组合项(屏幕适配)
//单位dp的参数:minWidth、minHeight、maxWidth、maxHeight
//单位px的参数:constraints.maxWidth
BoxWithConstraints {
//根据竖屏横屏返回不同的值
val margin = if (maxWidth < maxHeight) myContraints(16.dp) else myContraints(20.dp)
//传入约束集使用
ConstraintLayout(margin) {
//子元素设置约束集中对应的引用
Button(onClick = {}, modifier = Modifier.layoutId("button")) {
Text(text = "Button")
}
Text(text = "Text", modifier = Modifier.layoutId("text"))
}
}
}
五、脚手架 Scaffold
槽位(Slots API)会在界面中留出空位,让开发者自行填充使用。Scaffold可以为最常见的Material组件提供槽位(TopAppBar、BottomAppBar、FloatingActionBar、Drawer),使它们得到适当放置且正确的协同工作。
fun Scaffold(
modifier = Modifier,
scaffoldState = rememberScaffoldState(), //脚手架的状态(例如控件抽屉是否打开)
topBar = {}, //顶部标题栏,通常是一个TopAppBar
bottomBar = {}, //底部导航栏,通常是一个BottomNavigation
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) }, //用来展示SnackBar
floatingActionButton = {}, //悬浮按钮
floatingActionButtonPosition = FabPosition.End, //悬浮按钮位置
isFloatingActionButtonDocked = false, //如果存在BottomBar,那么是否与BottomBar重叠一半的高度
drawerContent = null, //抽屉的内容
drawerGesturesEnabled = true, //否允许手势控制抽屉的打开和关闭
drawerShape = MaterialTheme.shapes.large, //抽屉的形状
drawerElevation = DrawerDefaults.Elevation, //抽屉的阴影高度
drawerBackgroundColor = MaterialTheme.colors.surface, //抽屉的背景色
drawerContentColor = contentColorFor(drawerBackgroundColor), //抽屉内容的背景色
drawerScrimColor = DrawerDefaults.scrimColor, //抽屉组件打开时屏幕剩余部分的遮盖颜色
backgroundColor = MaterialTheme.colors.background, //脚手架的背景色
contentColor = contentColorFor(backgroundColor), //脚手架内容背景色
content: @Composable (PaddingValues) -> Unit //脚手架的内容
)
5.1 标题栏 TopBar
fun TopAppBar(
title: @Composable () -> Unit, //标题区域
modifier: Modifier = Modifier,
navigationIcon: @Composable () -> Unit = {}, //左侧导航图标区域,一般为IconButton或IconToggleButton
actions: @Composable RowScope.() -> Unit = {}, //右侧菜单区域,默认布局是Row,一般为IconButton
windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, //将尊重的窗口嵌入
colors: TopAppBarColors = TopAppBarDefaults.smallTopAppBarColors(), //用于不同状态下使用的颜色
scrollBehavior: TopAppBarScrollBehavior? = null //针对滚动的偏移设置
)
5.2 导航栏 BottomBar
fun BottomNavigation(
modifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.primarySurface, //背景色
contentColor: Color = contentColorFor(backgroundColor), //内容颜色
elevation: Dp = BottomNavigationDefaults.Elevation, //高度
content: @Composable RowScope.() -> Unit //包裹的内容
)
fun RowScope.BottomNavigationItem(
selected: Boolean, //是否选中
onClick: () -> Unit, //点击事件
icon: @Composable () -> Unit, //图标
modifier: Modifier = Modifier,
enabled: Boolean = true, //是否可点击
label: @Composable (() -> Unit)? = null, //名称
alwaysShowLabel: Boolean = true, //名称是否一直显示
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
selectedContentColor: Color = LocalContentColor.current, //选中时文本标签和图标的颜色以及波纹的颜色
unselectedContentColor: Color = selectedContentColor.copy(alpha = ContentAlpha.medium) //未选中时文本标签和图标的颜色
)
5.3 抽屉栏 Drawer
5.4 悬浮按钮 FloatingActionBar
5.5 SnackBar
Snackbar(
modifier: Modifier = Modifier,//布局修饰
action: @Composable (() -> Unit)? = null,//动作
actionOnNewLine: Boolean = false,//action是否应该放在单独的行上
shape: Shape = MaterialTheme.shapes.small,//Snackbar的形状和阴影
backgroundColor: Color = SnackbarDefaults.backgroundColor,//背景颜色
contentColor: Color = MaterialTheme.colors.surface,//内容颜色
elevation: Dp = 6.dp,//阴影效果
content: @Composable () -> Unit//内容
)
Snackbar(
snackbarData: SnackbarData,//通过SnackbarHostState显示的关于当前snackbar的数据
modifier: Modifier = Modifier,//布局修饰
actionOnNewLine: Boolean = false,//action是否应该放在单独的行上
shape: Shape = MaterialTheme.shapes.small,//Snackbar的形状和阴影
backgroundColor: Color = SnackbarDefaults.backgroundColor,//背景颜色
contentColor: Color = MaterialTheme.colors.surface,//内容颜色
actionColor: Color = SnackbarDefaults.primaryActionColor,//动作颜色
elevation: Dp = 6.dp//阴影效果
)
