Jetpack Compose中Modifier方法介绍

前言

在 Compose 中,Modifier 的调用顺序是有影响的。

修饰符列表

https://android-dot-google-developers.gonglchuangl.net/jetpack/compose/modifiers-list?hl=zh-cn

状态栏和底部导航栏

顶部状态栏

1
.statusBarsPadding()

底部导航栏

1
.navigationBarsPadding()

尺寸设置

固定大小

layout_width & layout_height => Modifier.size() or (Modifier.width() & Modifier.height())

size: 用于设置组件的固定大小。

1
2
3
4
5
Modifier.size(width = 100.dp, height = 100.dp)

Modifier.size(100.dp)

Modifier.width(300.dp).height(200.dp)

自身大小

默认是 wrap_content

适配父大小

match_parent =>

1
2
3
.fillMaxWidth()
.fillMaxHeight()
.fillMaxSize()

fillMaxHeight() 修饰符的行为是占据其父组件分配给它的最大可用高度

具体表现取决于父组件的布局特性和其他同级组件的布局情况:

  1. 如果父组件是一个Column(垂直布局容器):

    当 Column 使用默认的 Arrangement.Top 时,fillMaxHeight() 会让当前组件占据 Column 剩余的全部高度(即父组件高度减去前面同级组件占用的高度不考虑后面的组件

    当 Column 使用 Modifier.fillMaxHeight() 且设置 verticalArrangement = Arrangement.SpaceEvenly 等分配方式时,会根据排列规则分配高度

  2. 如果父组件是Box(层叠布局):

    • fillMaxHeight() 会让组件直接占据 Box 的全部高度,不受其他同级组件的影响(因为 Box 中的组件是层叠关系而非顺序排列)

剩余空间(权重)

权重只能在Row和Column中使用。

注意有多个元素,其中一个元素要占用剩余所有空间,这时候最好用.weight(1f)

因为

.fillMaxSize()计算的时候是之前元素的剩余空间,如果有三个元素,中间的元素就会占用除下第一个元素后的所有空间,第三个元素就没法显示了。

所以只有元素是最后一个元素的时候才能使用.fillMaxSize(),为了不出岔子都建议使用.weight(1f)

示例

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
Column(
modifier = Modifier
.fillMaxSize()
.statusBarsPadding()
.navigationBarsPadding()
.padding(start = CommonTheme.DpXl, end = CommonTheme.DpXl)
) {
// 顶部菜单
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(Color.Red)
) {

}
Spacer(Modifier.height(16.dp))
// 中部占用剩余空间
Row(
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.background(Color.Green)
) {

}

// 下部按钮
Spacer(Modifier.height(16.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.height(46.dp)
.background(Color.Blue)
) {

}
Spacer(Modifier.height(16.dp))
}

背景

基本

1
Modifier.background(Color.Green)

背景渐变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0xff8AEFFD),
Color(0xff3BD9FD),
Color(0xff32C8FD),
Color(0xff36B3FE),
Color(0xff50B8FF),
), // 定义垂直渐变色
startY = 0f,
),
shape = mShape
)
.clip(mShape)

渐变分割线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Composable
private fun LineVComp() {
Spacer(
modifier = Modifier
.width(0.5.dp)
.height(24.dp)
.background(
brush = Brush.verticalGradient(
colors = listOf(
Color(0x00ffffff),
Color(0xffffffff),
Color(0x00ffffff),
), // 定义垂直渐变色
startY = 0f,
),
)
)
}

内外边距和背景

在 Compose 中,背景色使用 Modifier.background() 进行设置。

在 Compose 中,Margin 和 Padding 都用 Modifier.padding() 来设置。

  • 没有background的时候是外边距
  • background的时候在background之前的是外边距,在background之后是内边距

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 背景色不包括 padding 的部分,效果类似 margin
Text(text = "Compose 学习", modifier = Modifier
.padding(8.dp)
.background(Color.Green))

// 背景色包括 padding 的部分,效果类似 padding
Text(text = "Compose 学习", modifier = Modifier
.background(Color.Green)
.padding(8.dp))

// 同时设置了 padding 和 margin 的效果
Text(
text = "Compose 学习", modifier = Modifier
.padding(8.dp)
.background(Color.Green)
.padding(8.dp)
)

background 还可以传入 shape 参数,来设置不同的背景形状。

Shape 对象也是一个通用的能力,例如,可以用于 clip 当中,进行裁切。

阴影

设置

1
2
3
4
5
6
7
8
.padding(10.dp)
.width(138.dp)
.shadow(
elevation = 6.dp,
shape = CommonTheme.CornerM,
ambientColor = Color(0x66BCC9E3)
)
.background(Color.White)

注意

shadow设置要在尺寸之后,要在background之前。

不能和clip搭配,会把阴影剪裁掉,设置shadow会自动剪裁背景。

最开始要设置padding,这样会给阴影预留空间。

Card阴影

1
2
3
4
5
6
7
8
9
10
11
12
Card(
elevation = CardDefaults.cardElevation(defaultElevation = 10.dp),
shape = RoundedCornerShape(8.dp),
colors = CardDefaults.cardColors(
containerColor = Color.White
),
modifier = Modifier
.padding(10.dp)
.size(150.dp, 80.dp)
) {
Text("Card 自带阴影", modifier = Modifier.align(Alignment.CenterHorizontally))
}

offset

offset: 用于将组件从其默认位置移动指定的偏移量。

偏移的元素不会影响后续元素的位置,相当于只是视觉上位置变了,实际位置没变。

1
Modifier.offset(x = 20.dp, y = 20.dp)

pading和offset

先看一个示例

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
Row(
modifier = Modifier
.align(Alignment.BottomCenter)
.offset(y = (-100).dp),
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
Box(
modifier = Modifier
.size(100.dp)
.background(Color.Black)
)

Box(
modifier = Modifier
.padding(20.dp)
.size(100.dp)
.background(Color.Red)
.padding(20.dp) // 内边距,内容会向内收缩
)

Box(
modifier = Modifier
.offset(y = 20.dp)
.size(100.dp)
.background(Color.Green)
.padding(20.dp) // 内边距,内容会向内收缩
)

Box(
modifier = Modifier
.size(100.dp)
.padding(20.dp)
.background(Color.Blue)
.padding(20.dp) // 内边距,内容会向内收缩
)
}

如上

红色和绿色的图形都偏移了相同的位置,并且大小是一样的。

padding是比较特殊的,它放在不同的位置的含义是不一样的

  • 在size前不会影响组件大小和背景一样大、相当于css中的margin。
  • 在size后背景前会压缩背景的大小。
  • 在背景后相当于css中的padding。
  • 当作用是margin的时候会影响后续元素的位置。

offset相当于偏移

  • 在Box中偏移值就相当于绝对定位。
  • 在Row/Column中会相对于原位置偏移。
  • 偏移的元素不会影响后续元素的位置。

裁剪

clip: 用于裁剪组件的内容,以匹配指定的形状。

1
Modifier.clip(shape = CircleShape)

注意

剪裁要放在background之前,否则背景不会被剪裁。

背景裁剪:只能通过设置背景形状实现。

1
2
3
4
5
6
7
Modifier
.padding(20.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(Color.White.copy(0.5f),RoundedCornerShape(16.dp))
.border(1.dp, Color.White, RoundedCornerShape(16.dp))
.padding(20.dp)

边框(border)

border: 用于向组件添加边框。

1
Modifier.border(width = 2.dp, color = Color.Black)

圆形边框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Box(
modifier = Modifier
.size(100.dp)
// 圆形边框:宽度为2dp,颜色为蓝色
.border(
width = 2.dp,
color = Color.Blue,
shape = CircleShape
)
.clip(CircleShape)
.background(Color.LightGray),
contentAlignment = Alignment.Center
) {
Text("圆形边框")
}

这点要注意的是

必须要先调用clip,再设置背景background,否则背景不会被剪裁,跟我们的直觉是相反的。

渐变边框

系统属性

1
2
3
4
5
6
7
8
9
10
11
12
13
.border(
width = 0.5.dp,
brush = Brush.linearGradient(
colors = listOf(
Color(0xFF70A0FF),
Color(0x00ffffff),
Color(0xFF5E5BFC)
),
start = androidx.compose.ui.geometry.Offset(0f, 0f),
end = androidx.compose.ui.geometry.Offset(0f, Float.POSITIVE_INFINITY)
),
shape = mShape
)

注意

Offset 的值不是0f100f,是 0fFloat.POSITIVE_INFINITY

自己绘制

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.StrokeJoin
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

/**
* 为组件添加渐变边框的Modifier
* @param brush 渐变画笔
* @param borderWidth 边框宽度
* @param cornerRadius 圆角半径
*/
fun Modifier.gradientBorder(
brush: Brush,
borderWidth: Dp,
cornerRadius: Dp = 12.dp
) = this.drawWithContent {
// 先绘制内容
drawContent()

// 计算边框的尺寸和位置(考虑边框宽度的一半作为偏移)
val borderWidthPx = borderWidth.toPx()
val cornerRadiusPx = cornerRadius.toPx()
val mRadius = CornerRadius(cornerRadiusPx, cornerRadiusPx)

// 创建边框路径
val path = Path().apply {
addRoundRect(
RoundRect(
left = borderWidthPx / 2,
top = borderWidthPx / 2,
right = size.width - borderWidthPx / 2,
bottom = size.height - borderWidthPx / 2,
topLeftCornerRadius = mRadius,
topRightCornerRadius = mRadius,
bottomLeftCornerRadius = mRadius,
bottomRightCornerRadius = mRadius
)
)
}

// 绘制渐变边框
drawPath(
path = path,
brush = brush,
style = Stroke(
width = borderWidthPx,
cap = StrokeCap.Round,
join = StrokeJoin.Round
)
)
}

/**
* 带渐变边框的组件示例
*/
@Composable
fun GradientBorderBox(
modifier: Modifier = Modifier,
borderWidth: Dp = 1.dp,
cornerRadius: Dp = 12.dp,
content: @Composable () -> Unit
) {
// 定义渐变颜色
val gradientBrush = Brush.linearGradient(
colors = listOf(
Color(0xFF70A0FF),
Color(0x00ffffff),
Color(0xFF5E5BFC)
),
start = Offset(0f, 0f),
end = Offset(0f, Float.POSITIVE_INFINITY),
)

Box(
modifier = modifier
.gradientBorder(
brush = gradientBrush,
borderWidth = borderWidth,
cornerRadius = cornerRadius,
)
) {
content()
}
}

使用

1
2
3
4
5
6
GradientBorderBox(
modifier = Modifier
.fillMaxWidth()
.height(106.dp)
) {
}

透明度

1
Modifier.alpha(0.5f)

对齐

内部对齐

Box

1
2
3
4
5
6
7
8
9
10
11
12
contentAlignment = Alignment.Center
contentAlignment = Alignment.TopStart // 左上
contentAlignment = Alignment.TopCenter // 上中
contentAlignment = Alignment.TopEnd // 右上
contentAlignment = Alignment.BottomStart // 左下
contentAlignment = Alignment.BottomCenter // 下中
contentAlignment = Alignment.BottomEnd // 右下

contentAlignment = Alignment.CenterStart // 左中
contentAlignment = Alignment.CenterEnd // 右中

contentAlignment = Alignment.Center // 中间

Row

1
2
3
verticalAlignment = Alignment.Top
verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.Bottom

Column

1
2
3
horizontalAlignment = Alignment.Start
horizontalAlignment = Alignment.CenterHorizontally
horizontalAlignment = Alignment.End

子相对父对齐

align: 用于指定组件在其父容器中的对齐方式。

align 方法用于指定组件在其父容器中的对齐方式。它适用于容器类组件,如 BoxColumnRow 等,以及具有布局属性的组件,如 BoxWithConstraints

设置组件内的元素的对齐方式不能用Modifier,会有专门的属性来配置。

align 方法生效的情况取决于父容器的布局方式。

通常情况下,父容器需要使用相应的布局修饰符,如 Box 中的 BoxScopeColumn 中的 ColumnScopeRow 中的 RowScope

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Box(
modifier = Modifier
.size(100.dp)
.background(Color.Red)
) {
Box(
modifier = Modifier
.size(50.dp)
.background(Color.Blue)
.align(Alignment.Center)
) {
// Content
}
}

Column中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Column(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Box(
modifier = Modifier
.size(50.dp)
.background(Color.Blue)
.align(Alignment.CenterHorizontally)
) {
// Content
}
}

事件

clickable: 用于使组件可点击,并指定点击事件的处理程序。

1
Modifier.clickable(onClick = { /* 点击事件处理 */ })

pointerInput: 用于处理指针输入事件,例如触摸或鼠标事件。

1
Modifier.pointerInput { /* 处理指针输入事件的逻辑 */ }

文字大小

文字大小使用函数参数(fontSize)设置,而不是 Modifier

颜色转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import androidx.compose.ui.graphics.Color

object ZColorUtils {
fun hexToColor(hex: String): Color {
val tempStr = hex.removePrefix("#")
if (tempStr.length == 6) {
val r: Int = tempStr.substring(0, 2).toLong(16).toInt()
val g: Int = tempStr.substring(2, 4).toLong(16).toInt()
val b: Int = tempStr.substring(4, 6).toLong(16).toInt()
return Color(r, g, b, 255)
} else if (tempStr.length == 8) {
val r: Int = tempStr.substring(0, 2).toLong(16).toInt()
val g: Int = tempStr.substring(2, 4).toLong(16).toInt()
val b: Int = tempStr.substring(4, 6).toLong(16).toInt()
val a = tempStr.substring(6, 8).toLong(16).toInt()
return Color(r, g, b, a)
}
return Color.White
}
}