这是我参与8月更文挑战的第1天,活动详情查看:8月更文挑战
这篇学习笔记住主要是根据官方文档学习Android Compose中的布局,通过学习这边笔记,可以大概了解Compose中Row,Column和Box三种布局,同时还会学习到如何设置布局的背景颜色,大小等属性。(Compose 布局基础知识 | Jetpack Compose | Android Developers)点击此链接可以直接查看官方文档,下面进入正文:
概述
这篇学习笔记主要是学习Compose中布局相关的只是,主要内容都是来自官方文档,可以直接点击上面的链接访问官方文档。
目标
Compose中的布局系统主要为了实现两个目标:
-
实现高性能。(在普通的Android的View体系中,我们都是应该尽量避免布局的多层嵌套,因为多层嵌套会导致布局的多次绘制,从而影响性能,但是Compose中布局的测量方式称为固有特性测量,通过避免多次测量布局子级实现高性能。)这里是基本的概念,具体怎么做还需要后面进行学习。
-
让开发者能够轻松地编写自定义布局。(在普通的Android的View体系中,自定义布局是比较繁琐的,一方面是要考虑
measure的过程,另一方面是要考虑layout的过程。)同样是基本概念,需要后面学习之后进行对比。
可组合函数
可组合函数是Compose的基本构建块,返回Unit的函数,这种函数用于描述界面中的某一部分。这种函数可以接受一些输入,然后根据这些输入的内容生成显示在屏幕上的信息。
一个可组合函数可能会发出多个界面元素,如果我们没有对这些元素指定一个恰当的布局,那么最终呈现的效果可能不是我们想要的那样。下面的代码演示了在一个可组合函数中发出两个文本,最终呈现的效果可能不是我们想要的那样:
@Composable
private fun noLayout(){
Text(text = "the first text")
Text(text = "the second test")
}
上面的可组合函数中由于没有提供如何排列这两个元素的布局,因此最终的结果是这两个Text将会堆叠在一起,类似于FrameLayout的效果:
垂直排列元素(Column)
使用Column可以将多个元素垂直地放置在屏幕上,类似于LinearLayout中oritation:vertical的效果。
@Composable
private fun columnLayout(){
Column() {
Text(text = "the first text",fontSize = 20.sp,color = Color.Black)
Text(text = "the second test",fontSize = 16.sp,color = Color.Red)
}
}
上面的代码中在两个Text的外部使用了Column进行包裹,下面是使用Column的效果:
水平排列元素(Row)
使用Row可以将其中的子项水平地放置在屏幕上,类似于LinearLayout中的oritation:horitation的效果:
@Composable
private fun rowLayout(){
Row() {
Text(text = "the first text",fontSize = 20.sp,color = Color.Black)
Text(text = "the second test",fontSize = 16.sp,color = Color.Red)
}
}
效果如下:
层叠排列元素(Box)
使用Box可以将一个元素放置在另一个元素之上,如下所示:
@Composable
private fun boxLayout(){
Box() {
Text(text = "the first text",fontSize =30.sp,color = Color.Black)
Text(text = "the second test",fontSize = 16.sp,color = Color.Red)
}
}
效果如下:
虽然上面的效果看起来和不使用任何的效果很相似,但是通过使用Box可以指定一些额外的参数,这是不使用任何布局达不到的效果,如下面的代码指定了子项的对齐方式:
@Composable
private fun boxLayout(){
Box(contentAlignment = Alignment.Center) {
Text(text = "the first text",fontSize = 30.sp,color = Color.Black)
Text(text = "the second test",fontSize = 12.sp,color = Color.Red)
}
}
效果如下:
通常情况下使用上面的这三个构建块基本就可以满足需求,通过可组合函数,配合这些布局便可以实现相应的需求,同时由于不需要考虑多重绘制的问题,我们可以任意组合这些布局而基本不需要考虑性能问题。
完善参数
上面我们演示了在Box中设置参数来改变子项的位置信息,在Column和Row中我们也可以指定相应的参数来达到不同的效果。通过指定horizontalArrangement和verticalAlignment参数(对于Row来说)或者指定verticalArrangement和horizontalAlignment参数(对于Column)来说,可以指定其子项的位置。
- 下面的代码指定了
Column中子项在垂直方向上处于底部,在水平方向上居中的效果:
@Composable
private fun columnLayoutChild(){
Column(
verticalArrangement = Arrangement.Bottom,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Text(text = "the first text",fontSize = 30.sp,color = Color.Black)
Text(text = "the second test",fontSize = 12.sp,color = Color.Red)
}
}
由于Column默认是按照子项的大小来调整自身的大小,因此这里通过modifer参数来指定Column的大小为充满其父容器,否则verticalArrangement = Arrangement.Bottom则没有效果。
- 下面的代码指定了
Row中子项的位置:
@Composable
private fun rowLayoutChild(){
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End,
modifier = Modifier.fillMaxSize()
) {
Text(text = "the first text",fontSize = 30.sp,color = Color.Black)
Text(text = "the second test",fontSize = 12.sp,color = Color.Red)
}
}
在上面的代码中,首先指定了Row的大小为布满其父容器,然后指定了Row中子项在水平方向上处于最右边,在垂直方向上居中,效果如下:
- 下面是我们熟悉的在
Row和Column指定weight属性,如下所示:
@Composable
private fun columnWeightChild() {
Column(modifier = Modifier.fillMaxSize()) {
Text(
text = "the first text",
fontSize = 30.sp,
color = Color.Black,
modifier = Modifier.weight(1f)
.background(color = Color.LightGray)
)
Text(
text = "the second test",
fontSize = 12.sp,
color = Color.Red,
modifier = Modifier.weight(1f)
.background(color = Color.DarkGray)
)
}
}
效果如下:
修饰符
在上面完善参数的部分,我们已经认识了一些属性,其实就和我们在xml布局中定义属性是一样的,我们需要添加更多的效果,则需要更多的属性,下面将会学习系统为我们提供的更多的属性,也称作修饰符。
修饰符是标准的Kotlin对象,可以通过调用某个Modifier类函数来创建修饰符,也可以将多个修饰符函数连接起来形成组合效果。
size指定大小
size修饰符我们已经在上面使用到了,通过这个修饰符我们可以指定某一项的大小,如下所示:
@Composable
private fun sizeColumn(){
Column(
modifier = Modifier
.size(width = 200.dp, height = 200.dp)
.background(color = Color.Red),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "宽度和高度均为200dp",
color = Color.White
)
}
}
在上面的代码中,我们就给Column指定了大小,宽度和高度均为200dp,另外我们还指定了颜色,为了方便查看效果。可以看到size和background属性均为Modifier类函数,这两个函数可以组合使用来达到我们想要的效果。下面是运行结果:
上面演示了指定Column大小的情况。默认情况下,子项应该遵从父项的约束,也就是子项的大小不能超过父项的大小。默认情况下,子项如果超过父项的大小,则不会生效,如下所示:
@Composable
private fun bigParentSize() {
Box(
modifier = Modifier
.size(300.dp, 600.dp)
.background(color = Color.LightGray),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier
.size(200.dp)
.background(color = Color.DarkGray),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "子项比父项大",
color = Color.Red,
modifier = Modifier
.size(width = 50.dp, height = 300.dp)
.background(color = Color.Green)
)
}
}
}
在上面的代码中,我们指定了Column的宽度和高度均为200dp,其中的子项Text的高度为300dp,比父项要大。理论上我们应该看不到Text里面的内容,或者只能看到一部分,因为有一部分内容在父项的上面,但是其实我们仍然能够完整看到Text中的内容,因为此时子项的大小受到父项的约束,子项本身的高度不能超过父项的高度。效果如下:
requiredSize突破父项的大小限制
如果我们就是不想要父项的约束,保持子项的大小,那么我们在设置大小的时候应该使用requiredSize(),使用此方法指定的大小不会受到父项的约束:
@Composable
private fun ignoreParentSize() {
Box(
modifier = Modifier
.size(300.dp, 600.dp)
.background(color = Color.LightGray),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier
.size(200.dp)
.background(color = Color.Blue),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Bottom
) {
Text(
text = "子项比父项还要大很多哦",
color = Color.White,
fontSize = 40.sp,
modifier = Modifier
.requiredSize(width = 80.dp, height = 500.dp)
.background(color = Color.Green)
)
}
}
}
在上面的代码中,我们仍然设置了子项的高度超过了父项的高度,并且为了查看最终的效果,我们设置了Text的文字大小,以及在外部设置了一个更大的父项Box,最终的效果如下:
wrapContentSize设置子项对齐方式
在上面的代码中,我们将子项的大小设置地超过了父项的大小,最后发现由于文字默认从上到下显示,所以上半边的文字会看不到,只能看到下半边的文字,那如果我们希望能看到上半边的文字呢?则应该使用wrapContentSize属性,如下所示:
@Composable
private fun ignoreParentSize() {
Box(
modifier = Modifier
.size(300.dp, 600.dp)
.background(color = Color.LightGray),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier
.size(200.dp)
.background(color = Color.Blue),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Bottom
) {
Text(
text = "子项比父项还要大很多哦",
color = Color.White,
fontSize = 40.sp,
modifier = Modifier
.requiredSize(width = 80.dp, height = 500.dp)
.background(color = Color.Green)
.wrapContentSize(align = Alignment.BottomCenter)
)
}
}
}
我们仍然使用之前的代码,只是在子项Text中添加了wrapContentSize属性,并指定了其中的align为底部居中,这样就尽可能地让上半边的文字显示出来,效果如下:
需要注意的是:上面的wrapContentSize属性只能指定子项自身的对齐方式,上面是由于文字较少,指定底部居中对齐以后,似乎文字的上半部分能显示出来了,但是其实并不是,当文字多了以后还是看不到上半部分,如下所示:
可以看到,当文字变多了以后只能看到中间部分的文字,上下的文字都看不到了。
另外需要注意的是:当文字的数量较少时,可以使用wrapContentSize的align属性指定内部文字的对齐方式,但是当文字超过子项的高度的时候,这个参数也不会生效。如下所示,下面两个图片均是指定了align = Alignment.Center的效果:
另外,对于文字本身如何对齐的文字则应该使用textAlign属性去设置。
另外,如果只是想设置宽度或者高度,可以使用width(),height(),requiredWidth(),requiredHeight()等方法。
如果我们仅仅需要在宽度或者高度上填满父项,则可以使用fillMaxWidth(),fillMaxHeight(),fillMaxSize()方法,如下所示:
@Composable
private fun fillParent() {
Column() {
Box(
modifier = Modifier
.size(200.dp, 100.dp)
.background(color = Color.Red),
contentAlignment = Alignment.Center
) {
Text(
text = "宽度占满父项", color = Color.White, modifier = Modifier
.fillMaxWidth()
.background(color = Color.Gray)
)
}
Spacer(modifier = Modifier.height(10.dp))
Box(
modifier = Modifier
.size(200.dp, 100.dp)
.background(color = Color.Yellow),
contentAlignment = Alignment.Center
) {
Text(
text = "宽度占满父项",
color = Color.White,
modifier = Modifier
.fillMaxHeight()
.background(color = Color.Gray)
)
}
Spacer(modifier = Modifier.height(10.dp))
Box(
modifier = Modifier
.size(200.dp, 100.dp)
.background(color = Color.Blue),
contentAlignment = Alignment.Center
) {
Text(
text = "全部占满父项",
color = Color.White,
modifier = Modifier
.fillMaxSize()
.background(color = Color.Gray)
)
}
}
}
运行效果如下:
paddingFromBaseline基于文字基线设置距离
对于文字来说,如果想要基于文字基线来设置距离,则可以使用paddingFromBaseline:
@Composable
private fun distanceBaseline() {
Row() {
Box (
modifier = Modifier.background(color = Color.Gray)
){
Text(
text = "基于文字基线设置距离",
modifier = Modifier
.paddingFromBaseline(
top = 100.dp,
bottom = 50.dp,
)
.background(color = Color.Red),
color = Color.White
)
}
Spacer(modifier = Modifier.width(20.dp))
Box (
modifier = Modifier.background(color = Color.Gray)
){
Text(
text = "设置Padding",
modifier = Modifier
.padding(
top = 100.dp,
bottom = 50.dp,
)
.background(color = Color.Red),
color = Color.White
)
}
}
}
offset设置偏移量
通过设置offset可以设置相对于原始位置放置布局,偏移量可以是正数,也可以是负数,如下所示:
@Composable
private fun paddingAndOffset(){
Column {
Text(text = "这是一段文字",
modifier = Modifier
.background(color = Color.Gray)
.offset(x = 10.dp)
)
Text(text = "这是一段文字",
modifier = Modifier
.padding(start = 10.dp,top = 10.dp)
.background(color = Color.Red)
)
}
}
从上面的图片可以看出来,使用offset并不会改变可组合项的测量结果。
Compose中的类型安全
在Compose中,有些修饰符仅适用于某些可组合项,比如上面已经学习过的weight修饰符就仅适用于Row和Column.
Box中的matchParentSize
在使用Box布局的时候,如果我们希望子项能够填满父项而不对父项的大小做出影响,则应该使用matchParentSize修饰符,这个修饰符仅可作用于Box的作用域内,也就是Box的直接子项可以使用此修饰符,如下所示:
@Composable
private fun matchParentSizeInBox(){
Box(modifier = Modifier.background(color = Color.Gray)) {
Spacer(modifier = Modifier
.background(color = Color.Cyan)
.matchParentSize())
Text(text = "这是一个很大的文本框",modifier = Modifier.size(200.dp)
.wrapContentSize(align = Alignment.Center),
textAlign = TextAlign.Center,
)
}
}
上面的代码运行结果如下:
从上面的运行结果可以看出:Box中包含两个子项,分别是Spacer和Text,其中Spacer的大小设置为和父项保持一致,Text则拥有自己的大小。最终Box的大小设置为其所有子项中最大的那个子项的大小,而Spacer和大小也和最大的子项的大小一致(从背景颜色可以看出这点)。
之前我们学习了fllMaxSize能够使子项的大小和父项保持一致,但是使用fillMaxSize的问题在于子项会反过来对父项的大小造成影响,如下所示:
@Composable
private fun fillMaxSizeInBox(){
Box(modifier = Modifier.background(color = Color.Gray)) {
Spacer(modifier = Modifier
.fillMaxSize()
.background(color = Color.Cyan)
)
Text(text = "这是一个很大的文本框",
modifier = Modifier
.size(200.dp)
.background(color = Color.Green)
.wrapContentSize(align = Alignment.Center),
textAlign = TextAlign.Center
)
}
}
在上面的代码中,同样Text有自己的大小,Spacer则设置为占满父项的大小,但是其父项又没有具体的大小,因此这个属性会反向作用域其父项,让其父项也撑满它的父项的大小,最终的结果如下: