前言 在 Compose 中,Modifier 的调用顺序是有影响的。
修饰符列表
https://android-dot-google-developers.gonglchuangl.net/jetpack/compose/modifiers-list?hl=zh-cn
状态栏和底部导航栏 顶部状态栏
底部导航栏
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)
正方形 1 Modifier.fillMaxHeight().aspectRatio(1f )
自身大小 默认是 wrap_content
适配父大小 match_parent =>
1 2 3 .fillMaxWidth() .fillMaxHeight() .fillMaxSize()
fillMaxHeight() 修饰符的行为是占据其父组件分配给它的最大可用高度
具体表现取决于父组件的布局特性和其他同级组件的布局情况:
如果父组件是一个Column (垂直布局容器):
当 Column 使用默认的 Arrangement.Top 时,fillMaxHeight() 会让当前组件占据 Column 剩余的全部高度(即父组件高度减去前面同级组件占用的高度不考虑后面的组件)
当 Column 使用 Modifier.fillMaxHeight() 且设置 verticalArrangement = Arrangement.SpaceEvenly 等分配方式时,会根据排列规则分配高度
如果父组件是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)) }
传参 Modifier 不能同时传width和weight,渲染会出问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Composable private fun RowScope.TableTitleBlock (title: String = "" , width: Dp = 0. dp, weight: Float = 0 f) { var modifier: Modifier? = null if (weight > 0f ) { modifier = Modifier.weight(weight) } else if (width > 0 .dp) { modifier = Modifier.width(width) } else { modifier = Modifier.width(80 .dp) } Box( modifier = modifier, contentAlignment = Alignment.Center ) { ZTextColorSizeComp( title, CommonTheme.ColorBlackMain, 14 .sp, fontWeight = FontWeight.Bold, ) } }
使用示例
1 2 3 4 5 6 7 8 9 10 Row( Modifier .fillMaxWidth() .height(44 .dp) .background(CommonTheme.ColorItemBg), verticalAlignment = Alignment.CenterVertically ) { TableTitleBlock(title = "科目" , width = 80 .dp) TableTitleBlock(title = "知识点" , weight = 1f ) }
背景 基本 1 Modifier.background(Color.Green)
背景图片 示例 1 2 3 4 5 6 Modifier .fillMaxSize() .paint( painterResource(id = R.drawable.bg), contentScale = ContentScale.FillBounds )
扩展 1 2 3 4 5 6 fun Modifier.paintBg (@DrawableRes id: Int ) : Modifier = composed { paint( painterResource(id = id), contentScale = ContentScale.FillBounds ) }
使用
1 Modifier.paintBg(R.drawable.home_sx)
背景渐变 垂直渐变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 .clip(mShape) .background( brush = Brush.verticalGradient( colors = listOf( Color(0xff8AEFFD ), Color(0xff3BD9FD ), Color(0xff32C8FD ), Color(0xff36B3FE ), Color(0xff50B8FF ), ), startY = 0f , ), shape = mShape )
水平渐变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Box( Modifier .width(136 .dp) .height(36 .dp) .clip(CommonTheme.CornerFull) .background( brush = Brush.horizontalGradient( colors = listOf( Color(0xff0085F7 ), Color(0xff30E5FC ), ), startX = 0f , ), ), contentAlignment = Alignment.Center ) { TextColorSizeComp("历史报告" , Color.White, 16 .sp) }
倾斜渐变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import androidx.compose.ui.graphics.BrushModifier .background( brush = Brush.linearGradient( colors = listOf( Color(0xFF96CEFE ), Color(0xFF118FF8 ) ), start = androidx.compose.ui.geometry.Offset(0f , 0f ), end = androidx.compose.ui.geometry.Offset( Float .POSITIVE_INFINITY, Float .POSITIVE_INFINITY ) ), )
渐变分割线 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 Text(text = "Compose 学习" , modifier = Modifier .padding(8 .dp) .background(Color.Green)) Text(text = "Compose 学习" , modifier = Modifier .background(Color.Green) .padding(8 .dp)) 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 9 10 11 12 13 14 Box( modifier = Modifier .size(98 .dp) .shadow( elevation = 8 .dp, shape = CircleShape, clip = true , spotColor = Color(0x660F5FFF ) ) .background(Color.White), contentAlignment = Alignment.Center ) { }
注意
shadow设置要在尺寸之后,要在background之前。
不要和clip搭配,会把阴影剪裁掉。
设置shadow会自动剪裁背景,所以背景只用设置颜色就行不用设置形状。
如果没有空间显示阴影,要设置padding,这样会给阴影预留空间。
背景不能是半透明的会导致阴影非常难看。
参数说明 ambientColor 和 spotColor 是用于更精细控制阴影效果的两个参数,主要用于 Modifier.shadow()。
它们的作用如下:
ambientColor(环境光阴影颜色) 模拟环境光照射物体产生的「扩散阴影」,通常表现为范围较广、颜色较浅的阴影部分。 它代表了周围环境散射光形成的阴影,给人一种柔和、均匀的阴影效果。 默认值通常是带有透明度的黑色(如 Color.Black.copy(alpha = 0.1f)),可根据需求自定义。
spotColor(点光源阴影颜色) 模拟点光源(如单一方向的强光)照射物体产生的「聚焦阴影」,通常表现为范围较窄、颜色较深的阴影部分。 它代表了定向光源形成的阴影,能增强物体的立体感和深度感。 默认值通常是比 ambientColor 更深的透明黑色(如 Color.Black.copy(alpha = 0.2f))。
Card阴影 当我们不需要调整阴影颜色,又想快速有阴影效果,可以使用Card。
1 2 3 4 5 6 7 8 9 10 11 Card( elevation = CardDefaults.cardElevation(defaultElevation = 10 .dp), shape = CircleShape, colors = CardDefaults.cardColors( containerColor = Color.White, ), modifier = Modifier .size(98 .dp), ) { }
Card本身没有调整阴影颜色的属性,虽然可以使用Modifier来调整,但是没有意义,不如不用Card。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Card( elevation = CardDefaults.cardElevation(defaultElevation = 10 .dp), shape = RoundedCornerShape(16 .dp), colors = CardDefaults.cardColors( containerColor = Color.White ), modifier = Modifier .padding(10 .dp) .size(140 .dp, 140 .dp) .shadow( elevation = 6 .dp, shape = RoundedCornerShape(16 .dp), spotColor = Color(0xaaCCE7FF ) ) ) { }
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 1 Modifier.clip(shape = CircleShape)
注意
剪裁要放在background之前,否则背景不会被剪裁。
示例2 Box中的AndroidView直接作为相机的渲染载体,那么填充父的大小就会失效,这时候我们要在父组件中设置剪裁,才能达到预期效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Box( modifier = Modifier .fillMaxSize() .padding(start = leftRightSpace, end = leftRightSpace) .clip(RectangleShape) ) { AndroidView( factory = { previewView }, modifier = Modifier .fillMaxSize() ) }
边框(border) border : 用于向组件添加边框。
1 Modifier.border(width = 2 .dp, color = Color.Black)
顺序 1 2 3 .clip(RoundedCornerShape(16 .dp)) .background(Color.White.copy(0.5f ),RoundedCornerShape(16 .dp)) .border(1 .dp, Color.White, RoundedCornerShape(16 .dp))
圆形边框 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Box( modifier = Modifier .size(100 .dp) .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 的值不是0f 到 100f,是 0f 到 Float.POSITIVE_INFINITY
垂直渐变到指定位置 1 2 3 4 5 6 7 8 9 10 11 .border( width = 0.5 .dp, brush = Brush.verticalGradient( colorStops = arrayOf( 0.0f to CommonTheme.ColorOrange, 0.8f to CommonTheme.ColorOrange.copy(alpha = 0f ), 1.0f to CommonTheme.ColorOrange.copy(alpha = 0f ) ) ), shape = CommonTheme.CornerL )
水平渐变 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 .border( width = 2 .dp, brush = Brush.linearGradient( colors = listOf( Color(0xffBBB3FF ), Color(0x00BBB3FF ) ), start = androidx.compose.ui.geometry.Offset(0f , 0f ), end = androidx.compose.ui.geometry.Offset( Float .POSITIVE_INFINITY, 0f ) ), shape = CommonTheme.CornerL )
自己绘制 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.Boximport androidx.compose.runtime.Composableimport androidx.compose.ui.Modifierimport androidx.compose.ui.draw.drawWithContentimport androidx.compose.ui.geometry.CornerRadiusimport androidx.compose.ui.geometry.Offsetimport androidx.compose.ui.geometry.RoundRectimport androidx.compose.ui.graphics.Brushimport androidx.compose.ui.graphics.Colorimport androidx.compose.ui.graphics.Pathimport androidx.compose.ui.graphics.StrokeCapimport androidx.compose.ui.graphics.StrokeJoinimport androidx.compose.ui.graphics.drawscope.Strokeimport androidx.compose.ui.unit.Dpimport androidx.compose.ui.unit.dpfun 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) ) { }
透明度
对齐 内部对齐 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 方法用于指定组件在其父容器中的对齐方式。它适用于容器类组件,如 Box、Column、Row 等,以及具有布局属性的组件,如 BoxWithConstraints。
设置组件内的元素的对齐方式不能用Modifier,会有专门的属性来配置。
align 方法生效的情况取决于父容器的布局方式。
通常情况下,父容器需要使用相应的布局修饰符,如 Box 中的 BoxScope、Column 中的 ColumnScope 或 Row 中的 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) ) { } }
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) ) { } }
事件 clickable : 用于使组件可点击,并指定点击事件的处理程序。
1 Modifier.clickable(onClick = { })
pointerInput : 用于处理指针输入事件,例如触摸或鼠标事件。
1 Modifier.pointerInput { }
文字大小 文字大小使用函数参数(fontSize )设置,而不是 Modifier
滚动 垂直滚动 1 2 3 4 5 6 7 8 9 10 11 12 13 val vState = remember { ScrollState(0 ) }Column( modifier = Modifier .verticalScroll(vState) .padding(16 .dp), verticalArrangement = Arrangement.spacedBy(8 .dp) ) { for (i in 1 ..20 ) { Text("Item $i " , modifier = Modifier.padding(8 .dp)) } }
注意
垂直滚动的区域的子元素不能有高度填充父元素的,也不能有没设置高度的LazyColumn 或 LazyVerticalGrid,否则会报错崩溃。
注意一定要加remember { },否则渲染的时候会重置位置。
即使设置.wrapContentHeight()也不行
1 2 3 4 5 6 7 8 9 10 11 12 LazyVerticalGrid( columns = GridCells.Fixed(3 ), modifier = Modifier .fillMaxWidth() .wrapContentHeight(), horizontalArrangement = Arrangement.spacedBy(8 .dp), verticalArrangement = Arrangement.spacedBy(8 .dp), ) { items(mList.size) { ResultUserImageItem(mList[it]) } }
必须设定高度
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Composable private fun ResultUserImageList (mList: List <String >) { val space = 8 val lineNum = ceil(mList.size / 3.0 ).toInt() val height = (lineNum * 72 ).dp + ((lineNum - 1 ) * space).dp LazyVerticalGrid( columns = GridCells.Fixed(3 ), modifier = Modifier .fillMaxWidth() .height(height), horizontalArrangement = Arrangement.spacedBy(space.dp), verticalArrangement = Arrangement.spacedBy(space.dp), ) { items(mList.size) { ResultUserImageItem(mList[it]) } } }
也就是说
可垂直滚动的容器内,如果元素也会垂直滚动,必须设置明确的高度。
水平滚动 1 2 3 4 5 6 7 8 9 10 11 12 13 val hState = remember { ScrollState(0 ) }Row( modifier = Modifier .horizontalScroll(hState) .padding(16 .dp), horizontalArrangement = Arrangement.spacedBy(8 .dp) ) { for (i in 1 ..20 ) { Text("Item $i " , modifier = Modifier.padding(8 .dp)) } }
自动滚动到最后 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 val scrollState = remember { ScrollState(0 ) }LaunchedEffect(vm.resultMdStr.value) { scrollState.animateScrollTo(scrollState.maxValue) } Column( Modifier .fillMaxWidth() .weight(1f ) .padding(bottom = 16 .dp, end = 26 .dp) .verticalScroll(scrollState) ) { MarkdownText(markdown = vm.resultMdStr.value) }
常用工具 颜色转换 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.Colorobject 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 } }