可组合项不指定布局默认是Box堆叠。
一、线性布局(一维)
1.1 纵向 Colum
| inline fun Column( modifier = Modifier, verticalArrangement = Arrangement.Top, //子元素纵向排列规则 horizontalAlignment = Alignment.Start, //子元素横向排列规则 content: @Composable ColumnScope.() -> Unit ) |
1.1.1 使Column具备滑动能力
val scrollState = rememberScrollState()
Column(Modifier.verticalScroll(scrollState)) {
repeat(20) {
Text(text = "aaaaaaa")
}
}
1.2 横向 Row
| inline fun Row( modifier: Modifier = Modifier, horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, verticalAlignment: Alignment.Vertical = Alignment.Top, content: @Composable RowScope.() -> Unit ) |
二、网格布局(二维) Grid
详见。虽然减少了 Column/Row 嵌套,但 Grid 后期改起来灵活性不行,子元素多了协调比例会烦死人,只在页面正好是个简单的网格比例可以用用,属于封装了个细分场景。
三、堆叠布局 Box
子元素可通过 Modifier.align() 调整位置,通过 Modifier.zIndex() 调整叠层顺序(数值越大层级越高),通过 Modifier.matchParentSize() 在其它子元素测量完成后再匹配Box大小(即希望使某个子项与父项
Box同样大,而不影响Box尺寸)。
| inline fun Box( modifier: Modifier = Modifier, contentAlignment: Alignment = Alignment.TopStart, propagateMinConstraints: Boolean = false, content: @Composable BoxScope.() -> Unit ) |
四、流式布局 / 弹性布局
FlowColumn/FlowRow 更轻量,复杂空间分配用 FlexBox。
4.1 FlowColumn / FlowRow
详见。
4.2 FlexBox
详见。
五、脚手架 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
一般使用自定义组件,或使用应用顶栏 TopAppBar() 组件以实现跟随页面滚动折叠。
| TopAppBar() | @Composable fun TopAppBar( title: @Composable () -> Unit, //中间标题区域 modifier: Modifier = Modifier, navigationIcon: @Composable () -> Unit = {}, //左侧导航图标区域 actions: @Composable RowScope.() -> Unit = {}, //右侧菜单区域,布局是Row expandedHeight: Dp = TopAppBarDefaults.TopAppBarExpandedHeight, //标题栏高度 windowInsets: WindowInsets = TopAppBarDefaults.windowInsets, //标题栏将遵守的窗口边衬区 colors: TopAppBarColors = TopAppBarDefaults.topAppBarColors(), scrollBehavior: TopAppBarScrollBehavior? = null, //根据页面滚动的响应方式 ) |
| TopAppBarDefaults | @Composable // 容器颜色 // 折叠后状态栏颜色 // 左侧功能区颜色(导航图标) // 中间标题区颜色(标题文字) // 右侧功能区颜色(菜单图标) // 副标题颜色 |
| TopAppBarScrollBehavior | @Composable 固定不动。 |
| @Composable 向上滚动时收起,向下滚动时立即展开。 | |
| @Composable 向上滚动时收起,向下滚动必须滚动到列表顶部才会展开。 |
@Composable
fun TestScreen() {
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
Column(
Modifier
.fillMaxSize()
.background(AppColor.Red)
.nestedScroll(scrollBehavior.nestedScrollConnection)
) {
TopAppBar(
expandedHeight = AppDimen.Height_Category_ArtistItem,
scrollBehavior = scrollBehavior,
windowInsets = WindowInsets(0,0,0,0),
colors = TopAppBarDefaults.topAppBarColors(
containerColor = AppColor.Transparent,
scrolledContainerColor = AppColor.Transparent
),
title = {
TextButton({}) {
TextPrimary(
modifier = Modifier.fillMaxWidth().background(AppColor.Blue),
text = "应用栏"
)
}
},
navigationIcon = {
IconButton({}) {
Icon(
imageVector = Icons.Default.Menu,
contentDescription = "抽屉栏"
)
}
},
actions = {
IconButton({}) {
Icon(
imageVector = Icons.Default.AddCircle,
contentDescription = "添加"
)
}
IconButton({}) {
Icon(
imageVector = Icons.Default.MoreVert,
contentDescription = "更多"
)
}
}
)
val sheetState = rememberModalBottomSheetState()
ModalBottomSheet(
onDismissRequest = {},
sheetState = sheetState,
) {
LazyColumn(Modifier.fillMaxWidth()) {
items(50) {
TextPrimary("哈哈哈")
}
}
}
}
}
5.2 底栏 BottomBar
一般使用自定义组件,可使用应用底栏 BottomAppBar(官方页面)、导航栏 NavigationBar + NavigationBarItem(官方页面)、侧边导航栏 NavigationRail + NavigationRailItem (官方页面)、之前还有 BottomNavigation + BottomNavigationItem。前面都是只在底部显示,可用自适应导航(Compact在底部,Medium和Expande在侧边常显,因此不会跟抽屉栏冲突)(官方页面)(我的笔记)。
5.3 抽屉栏 Drawer
Scafford 在 material 包下可以设置 drawerContent,在 material3 包下已经没有。使用 ModalNavigationDrawer(官方页面)(我的笔记)。
5.4 悬浮按钮 FloatingActionBar
5.5 信息条 SnackBar
用作显示在屏幕底部的剪短通知,可在不中断用户体验的情况下提供有关反馈和操作。会在几秒后消失,还可以通过点击按钮来关闭。
- 确认操作:用户删除电子邮件后,弹出一个动作条来提示成功并提供撤销选项。
- 网络状态:当失去互联网连接后,弹出一个动作条来指明处于离线状态。
- 数据提交:成功提交后,系统会在通知栏中显示更改已成功保存。
| fun SnackbarHost( hostState: SnackbarHostState, modifier: Modifier = Modifier, snackbar: @Composable (SnackbarData) -> Unit = { Snackbar(it) } ) |
| suspend fun showSnackbar( message: String, //动作条文本内容 actionLabel: String? = null, //动作按钮名称 withDismissAction: Boolean = false, //是否显示关闭按钮 duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite //可指定Short、Long、Indefinite ): SnackbarResult //返回的结果有两个值:SnackbarResult.Dismissed 自动消失、SnackbarResult.ActionPerformed 用户点击了操作按钮 |
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(
snackbarHost = { SnackbarHost(
hostState = snackbarHostState,
// snackbar = {} //自定义动作条界面,会覆盖下方showSnackbar中设置的内容和点击监听
) }
) {
Box(
modifier = Modifier.padding(it).fillMaxSize(),
contentAlignment = Alignment.Center
) {
Button(
onClick = {
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = "动作条文本内容",
actionLabel = "操作按钮",
withDismissAction = true, //是否显示关闭按钮
duration = SnackbarDuration.Short //Short、Long、Indefinite
).let {
when (it) {
//动作条自动消失
SnackbarResult.Dismissed -> {}
//用户点击了操作按钮
SnackbarResult.ActionPerformed -> {}
}
}
}
}
) { Text("弹出") }
}
}
六、约束布局 ConstraintLayout
废弃玩意,下面官方页面都打不开了,不要再用了!早期 Compose 功能不完善用来过渡的,能实现的功能都有更好的替代。
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) //水平居中
}
)
}
}
6.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")
}
}
}
6.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
}
)
}
}
6.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) {})
}
}
6.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"))
}
}
}
本文介绍了JetpackCompose中几种主要的布局组件,包括Column(垂直布局),Row(水平布局),Box(堆叠布局)以及ConstraintLayout(约束布局)。ConstraintLayout允许更复杂的子元素定位和约束,支持Barrier和Guideline创建灵活布局。此外,还提到了Scaffold组件,它为常见的Material设计组件提供结构,如TopAppBar,BottomNavigation等。

478

被折叠的 条评论
为什么被折叠?



