页面布局是GUI应用开发的核心,决定着一个UI具体如何实现。今天将延着路线图来练习『降Compose十八掌』的第二招式,学习一下如何使用Compose中的布局来构建页面。
基础骨架
基础骨架是一个应用页面的最关键结构,可以视为一种基础结构,有了基础结构以后,再补充其他具体的细节就能拼凑出来整体页面。Scaffold 就属于这样的一种基础骨架。
Scaffold并不是Compose设计出来的,它是Material Design 中的一个基础结构,为复杂的用户界面提供了标准化的平台。它可以把诸如标题栏,内容区域,浮动按扭等不同的UI功能部分组合在一起,形成一个整体连惯的页面。Compose是完全符合Material Design的,因此这里的Scaffold是符合Material Design设计标准的一个实现。
Scaffold主要有四个部分:
topBar - 在最顶部的标题栏,可以显示标题,导航按扭,以及菜单。对Android熟悉的同学把它当成ActionBar就可以了。
bottomBar - 在最底部的工具栏,一般用来显示页面内部的下一级的Tab导航,或者当成工具栏放一些实用性操作。
floatingActionButton - 在右下角悬浮的操作按扭。因为右下角空间有限,所以一般把当前页面最主要的操作放在这里。比如说对于文档类,创建『+』按扭就可以放在这里。
content - 内容区域,就是用于显示页面主要内容的地方,无具体形式,需要开发者自己提供其他布局作为内容。唯一需要注意的是,内容Composable lambda有一个参数叫做innerPadding,这个参数的作用是Scaffold对其content区域加的padding,纯大多数情况下,是需要使用此参数的。
Scaffold并不难,很好用,看一个🌰就知道了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
@OptIn ( ExperimentalMaterial3Api :: class )
@Preview
@Composable
fun ScaffoldExample () {
var presses by remember { mutableIntStateOf ( 0 ) }
Scaffold (
topBar = {
TopAppBar (
colors = mediumTopAppBarColors (
containerColor = MaterialTheme . colorScheme . primaryContainer ,
titleContentColor = MaterialTheme . colorScheme . primary ,
),
title = {
Text ( "降Compose十八掌" )
}
)
},
bottomBar = {
BottomAppBar (
containerColor = MaterialTheme . colorScheme . primaryContainer ,
contentColor = MaterialTheme . colorScheme . primary ,
) {
Text (
modifier = Modifier . fillMaxWidth (),
textAlign = TextAlign . Center ,
text = "掌式要义" ,
)
}
},
floatingActionButton = {
FloatingActionButton ( onClick = { presses ++ }) {
Icon ( Icons . Default . Add , contentDescription = "Add" )
}
}
) { innerPadding ->
Column (
modifier = Modifier . padding ( innerPadding ),
verticalArrangement = Arrangement . spacedBy ( 16. dp ),
) {
Text (
modifier = Modifier . padding ( 8. dp ),
text =
"""
“降龙十八掌可说是【武学中的巅峰绝诣】,当真是无坚不摧、无固不破。虽招数有限,但每一招均具绝大威力。
北宋年间,丐帮帮主萧峰以此邀斗天下英雄,极少有人能挡得他三招两式,气盖当世,群豪束手。
当时共有“降龙廿八掌”,后经萧峰及他义弟虚竹子删繁就简,取精用宏,改为降龙十八掌,掌力更厚。
这掌法传到洪七公手上,在华山绝顶与王重阳、黄药师等人论剑时施展出来,王重阳等尽皆称道。”
""".trimIndent(),
)
}
}
}
是一个非常标准的Material Design页面。
注意: 如果仔细看Scaffold函数可以发现,前面提到的四大部分,并不是传入数据,而是传入函数。这是声明式代码与传统方式的最大的区别,也是声明式代码的精髓所在,只是声明一个可以用于产生数据的函数作为参数,而非直接把数据当作参数传过去。这样做的好处在于,框架代码可以在真正需要数据的时候通过调用函数来生成数据,避免了数据提前生成。
小结一下,Scaffold是一个非常强大的基础骨架,适用当作页面基础结构来使用,也就是说只能用它来实现一级页面。
布局管理器
有了页面的基础骨架后,就可以往里面填内容了,布局器管理器就是用于组织和管理其他布局和基础部件的约束器,方便对页面元素进行归类和整理。包括三个分类,一是基础布局,是最为基础也最为常用的管理器;二是高级布局,用于一些复杂场景的管理器;三是集合性布局,用于显示数据集合。我们分别来学习。
基础布局
最为基础的布局管理器就三个:Row(行式,水平方向依次排列)Column(列式,垂直方向依次排列)和Box(层叠式,在屏幕上层叠)。用一张图就明了:
如果有Android基础的同学可以进行类比,Row和Column就相当于LinearLayout,而Box相当于FrameLayout。
高级布局
一般情况下通过基础布局的组合能够实现绝大部分的UI页面,如果遇到更复杂的声明,那就要用更为强大的工具。
约束式布局(ConstraintLayout in Compose)
ConstraintLayout是谷歌推出的一个更为强大的布局,用约束(constraint)统一了概念,可以任意排列子布局。Compose中也是可以使用ConstraintLayout的 ,并且它可以替代Row,Column和Box。需要注意它并不是Compose的一部分,需要额外添加依赖:
1
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Composable
fun ConstraintLayoutContent () {
ConstraintLayout {
// Create references for the composables to constrain
val ( button , text ) = createRefs ()
Button (
onClick = { /* Do something */ },
// Assign reference "button" to the Button composable
// and constrain it to the top of the ConstraintLayout
modifier = Modifier . constrainAs ( button ) {
top . linkTo ( parent . top , margin = 16. dp )
}
) {
Text ( "Button" )
}
// Assign reference "text" to the Text composable
// and constrain it to the bottom of the Button composable
Text (
"Text" ,
Modifier . constrainAs ( text ) {
top . linkTo ( button . bottom , margin = 16. dp )
}
)
}
}
ConstraintLayout是非常强大的,可以参考前面的一篇文章 来了解它的使用方法,在Compose中使用与在View和XML中使用是一样的。
流式布局(Flow layouts)
流式布局 非常强大,也非常常用,它们能够自动折成多行或者多列。Row和Column只能一行或者一列,超出了父布局的宽度和高度后,就看不见了。但FlowRow和FlowColumn则可以自动折叠,变为多行或者多列。并且是智能折叠,不会让子元素只显示一半。这个有非常实用的场景,像显示一些新闻的标签时,就可以用FlowRow。
1
2
3
4
5
6
7
8
9
10
@Composable
private fun FlowRowSimpleUsageExample () {
FlowRow ( modifier = Modifier . padding ( 8. dp )) {
ChipItem ( "Price: High to Low" )
ChipItem ( "Avg rating: 4+" )
ChipItem ( "Free breakfast" )
ChipItem ( "Free cancellation" )
ChipItem ( "£50 pn" )
}
}
集合性布局
集合性布局 用于显示数据集合,通常都是数量比较多。因为集合数据比较多,远超一个屏幕所能显示得完,因此集合性布局的优势在于用少量的子布局,以复用的方式来把集合数据展示出来。主要有三类水平方向的LazyRow ),垂直方向的LazyColumn )以及格子式的LazyGrid。使用起来也非常的直观,在其LazyListScope 中为子元素生成布局就可以了:
1
2
3
4
5
6
7
8
9
10
11
12
LazyColumn {
items (
items = messages , // 这是集合
key = { message ->
// 指定一个用于唯一标记集合中每个元素的id
message . id
}
) { message ->
// 这里生成集合元素对应的布局
MessageRow ( message )
}
}
需要注意的地方就是有很多个扩展函数items,每个集合性布局都有一个,在import的时候一定要选择与布局对应的那个。另外就是为了能让Compose区别不同的数据元素,一定要给集合元素指定一个可以在此范围内唯一标记元素的id。要不然在编辑的时候可能会有问题。
常见基础部件
最为常用的就是文本(Text) ,图像(Image) 和图标(Icon) 了,都不复杂,看个例子就能明白怎么使用。需要说明一下的就是Image是用于显示的图片;而Icon是用于显示小的图标,一般都是矢量图标资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Composable
fun ArtistCard ( artist : Artist ) {
Row (
modifier = Modifier . padding ( 16. dp ),
verticalAlignment = Alignment . CenterVertically ,
horizontalArrangement = Arrangement . spacedBy ( 16. dp )
) {
Box {
Image ( painter = painterResource ( id = artist . image ), contentDescription = "Artist image" )
Icon (
modifier = Modifier . align ( Alignment . BottomEnd ),
imageVector = Icons . Filled . Check , contentDescription = "Check mark" ,
tint = MaterialTheme . colorScheme . surfaceTint
)
}
Column {
Text (
text = artist . name ,
style = MaterialTheme . typography . titleMedium ,
color = MaterialTheme . colorScheme . primary
)
Text (
text = artist . status ,
style = MaterialTheme . typography . titleSmall ,
color = MaterialTheme . colorScheme . secondary
)
}
}
}
实战练习
今天学习的内容比较多,Compose的布局非常之多 ,今天的内容只是深入浅出式的学习了一些基础。另外,强烈建议亲手操练一下,推荐官方出品的实战教程Jetpack Compose basics 和Basic layouts in Compose 。
参考资料