Compose系列文章,请点原文阅读。原文,是时候学习Compose了!
一个 有序的、不可变的修饰元素集合,用于添加装饰或者行为到Compose UI元素。例如background、padding 、点击事件等。或者给Text设置单行、给Button设置各种点击状态等行为。
我们先拿XML中的FrameLayout做下对比,如下,我们在xml文件中定义了一个 宽度填充满父容器,高度200dp,背景为黑色,内容边距为16dp的 FrameLayout:
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#000000"
android:padding="16dp" >
<-- 可配置子级元素 -->
FrameLayout>
那么在Compose中如何实现这样的UI呢?直接来看结果,了解下Modifier是如何配合Composable函数实现的:
@Composable
fun BoxDemo() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.background(Color.Black)
.padding(16.dp),
) {
//可配置子级元素
}
}
首先我们需要定义一个使用@Composable
注解的函数BoxDemo(),在该函数中使用Compose提供的Box()函数,来实现类似XML中的FrameLayout效果。Box()函数有一个Modifier参数,我们设置下Modifier的各种属性,例如宽度填充满父容器,可以使用.fillMaxWidth()
;高度200dp,可以使用.height(200.dp)
;背景为黑色,可以使用.background(Color.Black)
;边距16dp,可以使用.padding(16.dp)
;这样通过给Box()设置相关的修饰符,我们就可以实现所需的显示效果;
Modifier基本的使用就类似上文一样,设置或者组合不同的属性给UI函数即可。注意官方的描述中有序的一词,在使用的过程中属性定义的顺序确实会影响到UI的显示效果。我们在下文示例中再详细描述,接下来一起看下Modifier可配置的属性吧。
【目前基于alpha 11版本】Modifier可配置的属性太多了,而且分布在好几个包中,如有遗漏请自行查看Compose Modifier相关文档:
Modifier.width(width: Dp)
Modifier.widthIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)
Modifier.fillMaxWidth(fraction: Float = 1f)
Modifier.wrapContentWidth(align: Alignment.Horizontal = Alignment.CenterHorizontally, unbounded: Boolean = false)
Modifier.preferredWidth(width: Dp)
Modifier.preferredWidth(intrinsicSize: IntrinsicSize)
Modifier.preferredWidthIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)
以上就是宽度的相关属性,那么类似的,高度属性如下所示(就不再具体说明啦):
Modifier.height(height: Dp)
Modifier.heightIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)
Modifier.fillMaxHeight(fraction: Float = 1f)
Modifier.wrapContentHeight(align: Alignment.Vertical = Alignment.CenterVertically, unbounded: Boolean = false)
Modifier.preferredHeight(intrinsicSize: IntrinsicSize)
Modifier.preferredHeight(height: Dp)
Modifier.preferredHeightIn(min: Dp = Dp.Unspecified, max: Dp = Dp.Unspecified)
OK,宽高分别的属性定义都有了,假如我想同时控制宽高的属性呢?有!请看size相关函数:
Modifier.size(size: Dp)
Modifier.size(width: Dp, height: Dp)
Modifier.sizeIn(minWidth: Dp = Dp.Unspecified, minHeight: Dp = Dp.Unspecified, maxWidth: Dp = Dp.Unspecified, maxHeight: Dp = Dp.Unspecified)
Modifier.fillMaxSize(fraction: Float = 1f)
Modifier.wrapContentSize(align: Alignment = Alignment.Center, unbounded: Boolean = false)
Modifier.preferredSize(size: Dp)
Modifier.preferredSize(width: Dp, height: Dp)
Modifier.preferredSizeIn(minWidth: Dp = Dp.Unspecified, minHeight: Dp = Dp.Unspecified, maxWidth: Dp = Dp.Unspecified, maxHeight: Dp = Dp.Unspecified)
接下来到了设置填充(padding)的时间了:
Modifier.padding(start: Dp = 0.dp, top: Dp = 0.dp, end: Dp = 0.dp, bottom: Dp = 0.dp)
Modifier.padding(horizontal: Dp = 0.dp, vertical: Dp = 0.dp)
Modifier.padding(all: Dp)
Modifier.padding(padding: PaddingValues)
Modifier.absolutePadding(left: Dp = 0.dp, top: Dp = 0.dp, right: Dp = 0.dp, bottom: Dp = 0.dp)
以下两个是针对有Text情况下的填充,因为有BaseLine等相关属性(不了解Baseline的可以先百度搜索下:TextView Baseline先进行阅读),阅读类App对该属性要求可能比较严格,平常开发中貌似几乎没有使用过类似属性。下面简单说下这几个函数(以博主本人的语言功底应该是描述不清楚的,所以后文我们会根据示例实际演示下):
Modifier.paddingFromBaseline(top: Dp = Dp.Unspecified, bottom: Dp = Dp.Unspecified)
Modifier.paddingFromBaseline(top: TextUnit = TextUnit.Unspecified, bottom: TextUnit = TextUnit.Unspecified)
当子级元素有Baseline属性时,例如子级元素有Text,那么 .paddingFromBaseline(top = 50.dp) 表示文本 第一行 Baseline的位置距离父级 顶部 为50dp, .paddingFromBaseline(bottom= 50.dp) 表示文本 最后一行 Baseline的位置距离父级 底部 为50dp。请看下文示例2.1 。 (在API28,XML中TextView提供了 android:firstBaselineToTopHeight="" 属性,对应这个就可以很好理解了)
上文面只是讲解了单位为dp情况下的内容,当单位为TextUnit的时候道理类似,只是Text可以设置为16.sp或者16.em。
Modifier.paddingFrom(alignmentLine: AlignmentLine, before: Dp = Dp.Unspecified, after: Dp = Dp.Unspecified)
Modifier.paddingFrom(alignmentLine: AlignmentLine, before: TextUnit = TextUnit.Unspecified, after: TextUnit = TextUnit.Unspecified)
Modifier.relativePaddingFrom(alignmentLine: AlignmentLine, before: Dp = Dp.Unspecified, after: Dp = Dp.Unspecified)
接下来是margin的时间,啊?翻遍了文档,没有margin属性,是不是官方不建议我们使用margin这种方式了呢?可能是建议我们使用padding或者Spacer()函数来实现?那我们来看下offset,乍一看好像能实现类似的margin属性,但是实际情况不是这么用的,应该是让我们在做动画的时候使用该函数:
Modifier.offset(x: Dp = 0.dp, y: Dp = 0.dp)
Modifier.offset(offset: Density.() -> IntOffset)
Modifier.offsetPx(x: State = mutableStateOf(0f), y: State = mutableStateOf(0f))
Modifier.absoluteOffset(x: Dp = 0.dp, y: Dp = 0.dp)
Modifier.absoluteOffset(offset: Density.() -> IntOffset)
Modifier.absoluteOffsetPx(x: State = mutableStateOf(0f), y: State = mutableStateOf(0f))
Modifier.aspectRatio(ratio: Float, matchHeightConstraintsFirst: Boolean = false)
Box(
modifier = Modifier
.width(80.dp)
.aspectRatio(ratio = 2f, matchHeightConstraintsFirst = false),
)
androidx.compose.foundation.layout包中的修饰符属性我们先研究这么多,下一个包!
Modifier.alpha(alpha: Float)
Modifier.clip(shape: Shape)
Modifier.clipToBounds()
Modifier.drawOpacity(opacity: Float)
Modifier.drawShadow(elevation: Dp, shape: Shape = RectangleShape, clip: Boolean = elevation > 0.dp)
Modifier.shadow(elevation: Dp, shape: Shape = RectangleShape, clip: Boolean = elevation > 0.dp)
Modifier.rotate(degrees: Float)
Modifier.scale(scaleX: Float, scaleY: Float)
Modifier.scale(scale: Float)
背景边框相关
Modifier.background(color: Color, shape: Shape = RectangleShape)
Modifier.background(brush: Brush, shape: Shape = RectangleShape, alpha: Float = 1.0f)
Modifier.border(border: BorderStroke, shape: Shape = RectangleShape)
Modifier.border(width: Dp, color: Color, shape: Shape = RectangleShape)
Modifier.border(width: Dp, brush: Brush, shape: Shape)
点击事件相关
Modifier.clickable(enabled: Boolean = true, onClickLabel: String? = null, role: Role? = null, onLongClickLabel: String? = null, onLongClick: () -> Unit = null, onDoubleClick: () -> Unit = null, onClick: () -> Unit)
Modifier.clickable(enabled: Boolean = true, interactionState: InteractionState, indication: Indication?, onClickLabel: String? = null, role: Role? = null, onLongClickLabel: String? = null, onLongClick: () -> Unit = null, onDoubleClick: () -> Unit = null, onClick: () -> Unit)
滚动相关
在alpha11版本之前,滚动视图是ScrollableRow、ScrollableColumn实现的。在alpha 11版本,Modifier中添加了如下两个函数,在Row或者Column中,我们使用这两个函数就可以在子级元素过宽、过长情况下开启滚动模式了。
Modifier.horizontalScroll(state: ScrollState, enabled: Boolean = true, reverseScrolling: Boolean = false)
Modifier.verticalScroll(state: ScrollState, enabled: Boolean = true, reverseScrolling: Boolean = false)
首先我们实现如下布局,蓝色Box中所有子级元素居中处理,所以红色分割线是居中线,我们下文需要参考该线进行理解。绿色Box中按照默认布局方式分别添加了黄色Box和白色Text两个元素。
全部代码如下所示:
@Composable
fun BoxDemo() {
Box(
modifier = Modifier
.width(400.dp)
.height(120.dp)
.background(myBlue),
contentAlignment = Alignment.Center
) {
Box(
modifier = Modifier
.width(100.dp)
.height(100.dp)
.background(color = myGreen)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
.background(color = myYellow),
)
Text(
modifier = Modifier.wrapContentSize(),
text = "Compose",
color = Color.White
)
}
//水平分割线
Divider(
modifier = Modifier
.fillMaxWidth()
.height(2.dp)
.background(color = myRed)
)
}
}
当我们给绿色Box添加.paddingFromBaseline(top = 50.dp)
属性后,可以看到UI显示效果为:
文本:绿色Box的宽高为100dp,设置该属性后,表示Text的Baseline位置需要位于高度为50dp处,也就是在红线标志的位置,所以文本整体的摆放如上所示。
黄色Box:可以看到黄色Box并不是从绿色Box的50dp处开始摆放的,而是从Text顶部开始摆放。怎么理解?假如Text的Baseline到Text顶部的距离是10dp,那么目前博主也就是理解为.paddingFromBaseline(top = 50.dp)
等于 .padding(top = (50-10).dp)
(如果此处有疑问请一定及时告知博主,唯恐误导各位同学)。
当我们保留上述属性,但是去掉Text元素的时候,显示效果又如下所示,此时我们不妨理解为,没有Baseline属性的元素时,.paddingFromBaseline(top = 50.dp)
等于 .padding(top = 50.dp)
:
如何实现渐变色的效果呢,使用Brush,直接看代码:
@Composable
fun GradientBox() {
val colorList = arrayListOf(Color(0xFF25BC6B), Color(0xFFFFCA1C))
Box(
modifier = Modifier
.width(200.dp)
.height(50.dp)
.background(
brush = Brush.horizontalGradient(colorList),
shape = RoundedCornerShape(50)
)
)
}
如下所示,还有shape指定的圆角效果哦:
关于Modifier其实还有很多特殊情况我们可能都没有遇到,本文只是重点列出了博主在实际开发中遇到的一些基础问题,后续遇到其他情况欢迎各位一起探讨。
到进阶篇的话我们会涉及到动画、自定义视图、手势等相关内容,一起努力吧!!!