文章

Jetpack Compose:约束布局

一、ConstraintLayout

约束布局,使用前需要导入相应的依赖:

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

ConstraintLayou 最新版查询:https://developer.android.google.cn/jetpack/androidx/releases/constraintlayout?hl=zh-cn

1、创建与绑定引用

在传统的 View 视图开发中,约束布局通常在 XML 中为 View 组件设置 ID,然后使用 ID 作为索引来确定组件摆放的位置,而在 Compose 中可以创建引用并绑定至某个组件上,从而使用该引用来确定组件的摆放位置。

有两种方式可以创建引用,分别是 createRef()createRefs() ,后者最多可以创建 16 个引用,创建方式如下所示:

val buttonRef = remember { createRef() }
val (textRef, imgRef) = remember { createRefs() }

只有在 ConstraintLayout 的作用域中才可以使用 createRef 和 createRefs 创建引用

Modifier.constrainAs() 用于将引用绑定到组件上,并可以在 constrainAs() 方法的 Lambda 中设置对齐位置。

示例代码如下所示:

ConstraintLayout(
    modifier = Modifier
    	.size(300.dp)
    	.padding(10.dp)
    	.background(Color.LightGray)
) {
    // 创建两个引用
    val (helloRef, worldRef) = remember { createRefs() }
    Text(
        text = "Hello",
        style = MaterialTheme.typography.h3,
        // 绑定 helloRef 引用
        modifier = Modifier.constrainAs(helloRef) {
            // 这里 parent 指 Text 的父组件,即 ConstraintLayout
            top.linkTo(parent.top)
            end.linkTo(parent.end, 10.dp)
        }
    )
    Text(
        text = "World",
        style = MaterialTheme.typography.h4,
        // 绑定 worldRef 引用
        modifier = Modifier.constrainAs(worldRef) {
            top.linkTo(helloRef.bottom, 10.dp)
            end.linkTo(helloRef.end)
        }
    )
}

Modifier.constrainsAs() 的 Lambda 是一个ConstrainScope 作用域,在其中可以获取到当前组件的 top、bottom、start、end 和父组件 parent 等信息,并使用 linkTo 指定约束。

除了在 ConstrainScope 中指定约束外,还可以在 ContrainScope 中设置组件的宽高等参数,如下所示:

ConstraintLayout(
    modifier = Modifier
    	.size(300.dp)
    	.padding(10.dp)
    	.background(Color.LightGray)
) {
    val surfaceRef = remember { createRef() }
    Surface(
        color = Color.Yellow,
        modifier = Modifier.constrainAs(surfaceRef) {
            width = Dimension.matchParent
            height = Dimension.value(100.dp)
        }
    ) {}
}

constraintLayouCompose_1

Dimension 可选值如下表所示:

Dimension 可选值 功能
wrapContent() 根据内容自适应调整尺寸
matchParent() 铺满父组件
fillToConstraints() 根据约束信息拉伸尺寸
preferredWrapContent() 如剩余空间大于内容尺寸时,为自适应尺寸,否则为剩余空间尺寸
ratio(String) 根据字符串计算实际尺寸比例,如 “1:2”
percent(Float) 根据浮点数计算实际尺寸比例
value(Dp) 设置为固定尺寸
preferredValue(Dp) 如剩余空间大于固定尺寸时,为固定尺寸,否则为剩余空间尺寸

2、Barrier 分界线

将用户名和密码的文本和对应的输入框组合时,可能会遇到如下情况:

constraintLayouCompose_2

即输入框的左侧没有对齐。

为了能使输入框的左侧对齐并且自适应调整宽度,就需要用到 Barrier 组件,仅需在文本结束处添加一条分界线,如下所示:

ConstraintLayout(
    modifier = Modifier.fillMaxSize()
) {
    val (usernameTextRef, passwordTextRef, usernameInputRef,
         passwordInputRef, dividerRef) = remember { createRefs() }
    
    // 使用 createEndBarrier 创建一条结尾分界线,分界线会处于文本较长的文本组件结尾处
    val barrier = createEndBarrier(usernameTextRef, passwordTextRef)
    
    Text(
        text = "用户名",
        modifier = Modifier.constrainAs(usernameTextRef) {
            top.linkTo(parent.top, 30.dp)
            start.linkTo(parent.start, 10.dp)
        }
    )
    OutlinedTextField(
        value = "",
        onValueChange = {},
        modifier = Modifier
        .constrainAs(usernameInputRef) {
            // 将用户名输入框 start 位置设为分界线右 10dp 处
            start.linkTo(barrier, 10.dp)
            top.linkTo(usernameTextRef.top)
            bottom.linkTo(usernameTextRef.bottom)
        }

    )
    Text(
        text = "密码",
        modifier = Modifier.constrainAs(passwordTextRef) {
            top.linkTo(usernameInputRef.bottom, 30.dp)
            start.linkTo(parent.start, 10.dp)
        }
    )
    OutlinedTextField(
        value = "",
        onValueChange = {},
        modifier = Modifier
        .constrainAs(passwordInputRef) {
            // 将密码输入框 start 位置设为分界线右 10dp 处
            start.linkTo(barrier, 10.dp)
            top.linkTo(passwordTextRef.top)
            bottom.linkTo(passwordTextRef.bottom)
        }
    )
}

constraintLayouCompose_3

3、Guideline 引导线

Guideline 可以直接创建出一条引导线,下面是使用 Guideline 实现 Text 在屏幕水平垂直居中的例子:

ConstraintLayout(
    modifier = Modifier.fillMaxSize()
) {
    val textRef = remember { createRef() }
    // 创建一个从顶部开始的引导线,使引导线位于屏幕垂直居中
    val verticalGuideline = createGuidelineFromTop(.5f)
    // 创建一个从左侧开始的引导线,使引导线位于屏幕水平居中
    val horizontalGuideline = createGuidelineFromStart(.5f)
    Text(
        text = "垂直居中",
        modifier = Modifier.constrainAs(textRef) {
            // 将 Text 连接至引导线
            top.linkTo(verticalGuideline)
            bottom.linkTo(verticalGuideline)
            start.linkTo(horizontalGuideline)
            end.linkTo(horizontalGuideline)
        }
    )
}

constraintLayouCompose_4

4、Chain 链接约束

通过 Chain 链接约束可以使多个组件平均分配布局空间,功能类似于 weight。

Chain 提供了三种样式可供选择,如下所示:

Chain 布局样式 功能
ChainStyle.Spread 每个元素平分整个父空间
ChainStyle.SpreadInside 首位元素紧贴边界,剩余元素平分父空间
ChainStyle.Packed 所有元素聚集到中间

下面展示使用 Chain 将四个按钮根据 Chain 提供的不同的布局样式进行排布。

ConstraintLayout(
    modifier = Modifier.fillMaxSize()
) {
    val (btn1Ref, btn2Ref, btn3Ref, btn4Ref) = remember { createRefs() }
    // 创建一条垂直的约束链接将四个按钮连接起来,并指定链接样式
    createVerticalChain(btn1Ref, btn2Ref, btn3Ref,btn4Ref, chainStyle = ChainStyle.Packed)

    Button(
        onClick = {},
        // 绑定样式,下同
        modifier = Modifier.constrainAs(btn1Ref) {}
    ) {
        Text("Btn1")
    }
    Button(
        onClick = {},
        modifier = Modifier.constrainAs(btn2Ref) {}
    ) {
        Text("Btn1")
    }
    Button(onClick = {},
        modifier = Modifier.constrainAs(btn3Ref) {}
    ) {
        Text("Btn1")
    }
    Button(
        onClick = {},
        modifier = Modifier.constrainAs(btn4Ref) {}
    ) {
        Text("Btn1")
    }
}

Packed:
constraintLayouCompose_5

SpreadInside:
constraintLayouCompose_6

Spread:
constraintLayouCompose_7

License:  CC BY 4.0