概述
我们都知道ConstraintLayout在构建嵌套层级复杂的视图界面时可以有效降低视图树的高度,使视图树扁平化,约束布局在测量布局耗时上比传统的相对布局具有更好的性能,并且约束布局可以根据百分比自适应各种尺寸的终端设备。因为约束布局确实很好用,所以,官方也为我们将约束布局迁移到了Compose平台。本文就是介绍约束布局在Compose中的使用。
实例解析
在使用约束布局之前,我们需要先在项目中的app.gradle脚本中添加compose版本的ConstraintLayout依赖
implementation('androidx.constraintlayout:constraintlayout-compose:1.0.1')
引入依赖以后,我们就可以看下如何在Compose中使用约束布局了
1.创建引用
在传统View系统中,我们在布局XML文件中可以给View设置资源的ID,并将资源ID作为索引来声明对应组件的摆放位置。而在Compose的约束布局中,可以主动创建引用并绑定到某个具体组件上,从而实现与资源ID相似的功能,每个组件都可以利用其他组件的引用获取到其他组件的摆放位置信息,从而确定自己摆放位置。
Compose 创建约束布局的方式有两种,分别时createRef()和createRefs(),根据字面意思我们就可以很清楚的知道,createRef(),每次只会创建一个引用,而createRefs()每次可以创建多个引用(最多可以创建16个),创建引用的方式如下:
// 创建单个引用
val text = createRef()
// 创建多个引用
val (button1,button2,text) = createRefs()
2.绑定引用
当我们创建完引用后就可以使用Modifier.constrainAs()修饰符将我们创建的引用绑定到某个具体组件上,可以在contrainAs尾部Lambda内指定组件的约束信息。我们需要注意的是,我们只能在ConstraintLayout尾部的Lambda中使用createRefer,createRefs函数创建引用,并使用Modifier.constrainAs函数来绑定引用,因为ConstrainScope尾部的Lambda的Reciever是一个ConstraintLayoutScope作用域对象。我们可以先看下面一段代码了解下约束布局的引用绑定:
@Composable
fun ConstrainLayoutDemo()
{
ConstraintLayout(modifier = Modifier
.width(300.dp)
.height(100.dp)
.padding(5.dp)
.border(5.dp, color = Color.Red)) {
val portraitImageRef = remember {
createRef()
}
Image(painter = painterResource(id = R.drawable.portrait)
, contentDescription = null,
modifier = Modifier.constrainAs(portraitImageRef){
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
})
}
}
运行结果:
上面的代码是实现一个用户卡片的部分代码,从代码中看到我们使用约束的时候需要用到Modifier.constrainsAs(){……}的方式。Modifier.constrainsAs的尾部Lambda是一个ConstrainScope作用域对象,可以在其中获取当前组件的parent,top,bottom,start,end等信息。并使用linkTo指定组件约束。在上面的界面中,我们希望用户的头像可以居左对齐,所以将top拉伸至父组件的顶部,bottom拉伸至父组件的底部,start拉伸至父组件的左边。我们再为卡片添加上用户的昵称和描述,全部代码如下所示:
@Composable
fun ConstrainLayoutDemo()
{
ConstraintLayout(modifier = Modifier
.width(300.dp)
.height(100.dp)
.padding(5.dp)
.border(5.dp, color = Color.Red)) {
val (portraitImageRef,usernameTextRef,descriptionTextRef) = remember {
createRefs()
}
Image(painter = painterResource(id = R.drawable.portrait)
, contentDescription = null,
modifier = Modifier.constrainAs(portraitImageRef){
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
})
Text(text = "旅游小美女", fontSize = 16.sp, maxLines = 1,
textAlign = TextAlign.Left,
modifier = Modifier.constrainAs(usernameTextRef){
top.linkTo(portraitImageRef.top)
start.linkTo(portraitImageRef.end,10.dp)
})
Text(text = "个人描述。。。。。。。。", fontSize = 14.sp,
color = Color.Gray,
fontWeight = FontWeight.Light,
modifier = Modifier.constrainAs(descriptionTextRef){
top.linkTo(usernameTextRef.bottom,5.dp)
start.linkTo(portraitImageRef.end,10.dp)
}
)
}
}
运行结果:
在上面的代码中我们也可以在ConstrainScope中指定组件的宽高信息,在ConstrainScope中直接设置width与height的可选值如下所示:
在ConstrainScope中指定组件的宽高信息时,通过在
Modifier.constrainAs(xxxRef){width = Dimension.可选值}
来设置,可选值如下:
Dimension.wrapContent: 实际尺寸为根据内容自适应
Dimension.matchParent: 实际尺寸为铺满父组件的尺寸
Dimension,wrapContent: 实际尺寸为根据约束信息拉伸后的尺寸
Dimension.preferredWrapContent: 如果剩余空间大于更具内容自适应的尺寸时,实际尺寸为自适应的尺寸,如果剩余空间小于内容自适应尺寸时,实际尺寸为剩余空间尺寸
Dimension.ratio(String): 根据字符串计算实际尺寸所占比率:如1 :2
Dimension.percent(Float): 根据浮点数计算实际尺寸所占比率
Dimension.value(Dp): 将尺寸设置为固定值
Dimension.perferredValue(Dp): 如果剩余空间大于固定值时,实际尺寸为固定值,如果剩余空间小于固定值时,实际尺寸则为剩余空间尺寸
我们想象下,假如用户的昵称特别长,那么按照我们上面的代码展示则会出现展示不全的问题,所以我们可以通过设置end
来指定组件所允许的最大宽度,并将width设置为preferredWrapContent
,意思是当用户名很长时,实际的宽度会做自适应调整。我们将上面展示用户名的地方改一下,代码如下:
// 上面的代码只用改这个部分
Text(
text = "旅游小美女美美美美美名字很长长长长长长长长长",
fontSize = 16.sp,
textAlign = TextAlign.Left,
modifier = Modifier.constrainAs(usernameTextRef){
top.linkTo(portraitImageRef.top)
start.linkTo(portraitImageRef.end,10.dp)
end.linkTo(parent.end,10.dp)
width = Dimension.preferredWrapContent
})
运行结果:
辅助布局工具
在传统View的约束布局中有Barrier,GuideLine等辅助布局的工具,在Compose中也继承了这些特性,方便我们完成各种复杂场景的布局需求。
1.Barrier分界线
Barrier顾名思义就是一个屏障,使用它可以隔离UI布局上面的一些相互挤压的影响,举一个例子,比如我们希望两个输入框左对齐摆放,并且距离文本组件中的最长者仍保持着10dp的间隔,当用户名和密码等发生变化时,输入框的位置能够自适应调整。这里使用Barrier特性可以简单的实现这一需求:
@Composable
fun InputFieldLayout(){
ConstraintLayout(
modifier = Modifier
.width(400.dp)
.padding(10.dp)
) {
val (usernameTextRef, passwordTextRef, usernameInputRef, passWordInputRef) = remember { createRefs() }
val barrier = createEndBarrier(usernameTextRef, passwordTextRef)
Text(
text = "用户名",
fontSize = 14.sp,
textAlign = TextAlign.Left,
modifier = Modifier
.constrainAs(usernameTextRef) {
top.linkTo(parent.top)
start.linkTo(parent.start)
}
)
Text(
text = "密码",
fontSize = 14.sp,
modifier = Modifier
.constrainAs(passwordTextRef) {
top.linkTo(usernameTextRef.bottom, 20.dp)
start.linkTo(parent.start)
}
)
OutlinedTextField(
value = "",
onValueChange = {},
modifier = Modifier.constrainAs(usernameInputRef) {
start.linkTo(barrier, 10.dp)
top.linkTo(usernameTextRef.top)
bottom.linkTo(usernameTextRef.bottom)
height = Dimension.fillToConstraints
}
)
OutlinedTextField(
value = "",
onValueChange = {},
modifier = Modifier.constrainAs(passWordInputRef) {
start.linkTo(barrier, 10.dp)
top.linkTo(passwordTextRef.top)
bottom.linkTo(passwordTextRef.bottom)
height = Dimension.fillToConstraints
}
)
}
}
运行结果:
2.Guideline引导线
Barrier分界线需要依赖其他引用,从而确定自身的位置,而使用Guideline不依赖任何引用,例如,我们希望将用户头像摆放在距离屏幕顶部2:8的高度位置,头像以上是用户背景,以下是用户信息,这样的需求就可以使用Guideline实现,代码如下:
@Composable
fun GuidelineDemo(){
ConstraintLayout(modifier = Modifier
.height(300.dp)
.background(color = Color.Gray)) {
val guideline = createGuidelineFromTop(0.2f)
val (userPortraitBackgroundRef,userPortraitImgRef,welcomeRef) = remember {
createRefs()
}
Box(modifier = Modifier
.constrainAs(userPortraitBackgroundRef) {
top.linkTo(parent.top)
bottom.linkTo(guideline)
height = Dimension.fillToConstraints
width = Dimension.matchParent
}
.background(Color(0xFF673AB7)))
Image(painter = painterResource(id = R.drawable.portrait),
contentDescription = null,
modifier = Modifier
.constrainAs(userPortraitImgRef) {
top.linkTo(guideline)
bottom.linkTo(guideline)
start.linkTo(parent.start)
end.linkTo(parent.end)
}
.size(100.dp)
.clip(CircleShape)
.border(width = 2.dp, color = Color(0xFF96659E), shape = CircleShape))
Text(text = "不喝奶茶的小白兔",
color = Color.White,
fontSize = 26.sp,
modifier = Modifier.constrainAs(welcomeRef){
top.linkTo(userPortraitImgRef.bottom,10.dp)
start.linkTo(parent.start)
end.linkTo(parent.end)
})
}
}
运行结果:
在上面的代码中,我们使用createGuidelineFromTop()方法创建了一条从顶部出发的引导线,然后用户背景就可以依赖这条引导线确定宽高了,然后对于头像,我们只需要将top和bottom连接至引导线即可
3.Chain链接约束
ContraintLayout的另一个好用的特性就是Chain链接约束,通过链接约束可以允许多个组件平均分配布局空间,类似于weight修饰符。例如我们要展示一首古诗,用Chain链接约束实现如下:
@Composable
fun showQuotesDemo() {
ConstraintLayout(
modifier = Modifier
.size(400.dp)
.background(Color.Black)
) {
val (quotesFirstLineRef, quotesSecondLineRef, quotesThirdLineRef, quotesForthLineRef) = remember {
createRefs()
}
createVerticalChain(
quotesFirstLineRef, quotesSecondLineRef, quotesThirdLineRef, quotesForthLineRef,
chainStyle = ChainStyle.Spread
)
Text(text = "窗前明月光,",
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.constrainAs(quotesFirstLineRef) {
start.linkTo(parent.start)
end.linkTo(parent.end)
})
Text(text = "疑是地上霜。",
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.constrainAs(quotesSecondLineRef) {
start.linkTo(parent.start)
end.linkTo(parent.end)
})
Text(text = "举头望明月,",
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.constrainAs(quotesThirdLineRef) {
start.linkTo(parent.start)
end.linkTo(parent.end)
})
Text(text = "低头思故乡。",
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.constrainAs(quotesForthLineRef) {
start.linkTo(parent.start)
end.linkTo(parent.end)
})
}
}
运行结果:
如上面代码所示,我们要展示四句诗就需要创建四个引用对应四句诗,然后我们就可以创建一条垂直的链接约束将四句诗词连接起来,创建链接约束时末尾参数可以传一个ChainStyle,用来表示我们期望的布局样式,它的取值有三个,效果和意义如下所示:
(1)Spread:链条中的每个元素平分整个父空间
createVerticalChain(
quotesFirstLineRef, quotesSecondLineRef, quotesThirdLineRef, quotesForthLineRef,
chainStyle = ChainStyle.Spread)
运行效果:
(2)SpreadInside:链条中的首尾元素紧贴边界,剩下的每个元素平分整个父空间
createVerticalChain(
quotesFirstLineRef, quotesSecondLineRef, quotesThirdLineRef, quotesForthLineRef,
chainStyle = ChainStyle.SpreadInside)
运行效果:
(3)Packed:链条中的所有元素都聚集到中间,效果如下
createVerticalChain(
quotesFirstLineRef, quotesSecondLineRef, quotesThirdLineRef, quotesForthLineRef,
chainStyle = ChainStyle.Packed)
运行效果:
总结
关于Compose约束布局的内容就是这些了,本文主要是简单的介绍了Compose中约束布局的基本使用,要熟练掌握Compose约束布局,还需要读者多去联系,多使用约束布局写界面,这样就会熟能生巧,在此我只做一个抛砖引玉的活。有任何疑问,欢迎在评论区交流。