Jetpack Compose (三) ——— Compose 约束布局

615 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

Compose 支持约束布局,在使用约束布局之前需要先通过 gradle 导入:

implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha02"

一、创建约束

当我们用 Android Studio 创建一个新项目时,Android Studio 会为我们默认生成一个约束布局,它包含一个 TextView,文字是 "Hello World!",位置居中。

如果用 Compose 实现同样的效果,代码如下:

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout(modifier = Modifier.fillMaxHeight().fillMaxWidth()) {
        val (text) = createRefs()
        Text("Hello World!", Modifier.constrainAs(text) {
            top.linkTo(parent.top)
            bottom.linkTo(parent.bottom)
            start.linkTo(parent.start)
            end.linkTo(parent.end)
        })
    }
}

首先,使用 val (text) = createRefs() 创建引用,然后在 Text() 函数的 Modifier 中,通过 Modifier.constrainAs(text) 将其与引用关联上。在只有一个控件时,暂时看不出建立引用有什么作用。所以建立引用的作用我们待会再讲。

建立引用后,通过 linkTo 函数,指定其上下左右四个方向的约束。这里和之前 xml 中定义约束的作用一样,只有写法上的区别。

运行效果如下:

HelloWorld

二、控件之间的约束

在添加控件与控件之间的约束时,就能看出建立引用的作用了。比如我们建立两个 Text 之间的上下约束,代码如下:

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout(modifier = Modifier.fillMaxHeight().fillMaxWidth()) {
        val (text1, text2) = createRefs()
        Text("Hello World!", Modifier.constrainAs(text1) {
            top.linkTo(parent.top)
            bottom.linkTo(text2.top)
            start.linkTo(parent.start)
            end.linkTo(parent.end)
        })
        Text("Hello World!", Modifier.constrainAs(text2) {
            top.linkTo(text1.bottom)
            bottom.linkTo(parent.bottom)
            start.linkTo(parent.start)
            end.linkTo(parent.end)
        })
    }
}

首先,通过 val (text1, text2) = createRefs() 建立两个引用,然后两个控件通过 Modifier.constrainAs(text1) 认领自己的引用。在添加约束条件时,就能取到每个引用的上下左右四个边了。

运行效果如下:

image.png

三、Barrier

在约束布局中,有个功能叫 Barrier,译为屏障。Compose 通过 createXXXBarrier 函数创建屏障:

val barrier1 = createTopBarrier(text1, text2)
val barrier2 = createBottomBarrier(text1, text2)
val barrier3 = createStartBarrier(text1, text2)
val barrier4 = createEndBarrier(text1, text2)

使用方式也很简单,添加 linkTo(barrier) 约束条件即可。

注:由于本文重点不是讲约束布局,只是讲 Compose 的约束布局和 xml 定义的约束布局的区别,所以不再细讲屏障的作用,下同。

四、Guideline

在约束布局中,Guideline 也很常用,Compose 通过 createGuidelineFromXXX 函数创建 Guideline:

val guideline1 = createGuidelineFromStart(0.5f)
val guideline2 = createGuidelineFromStart(100.dp)
val guideline3 = createGuidelineFromAbsoluteLeft(100.dp)

可以从上下左右四个方向创建 Guideline,可以通过百分比或固定长度来指定 Guideline 的位置。需要注意的是,通常我们指定左右时,都不是用 left/right,而是用 start/end。这是为了让应用便于被那些从右到左阅读的国家使用。这也是 createGuidelineFromAbsoluteLeft 和 createGuidelineFromStart 的区别。

使用方式同样是添加 linkTo(guideline) 约束条件即可。

五、解耦约束条件

前文说过,Compose 作为声明式 UI,不支持为控件设置 id,不能通过 id 获取到控件实例。

但在设置约束布局时,没有 id 实在是太不方便了,无法设置 id 导致约束无法被复用。所以使用约束布局时,可以给控件设置 id。

@Composable
fun ConstraintLayoutContent() {
    ConstraintLayout(
        constraintSet(),
        modifier = Modifier
            .fillMaxHeight()
            .fillMaxWidth()
    ) {
        Text("Hello World!", Modifier.layoutId("text1"))
        Text("Hello World!", Modifier.layoutId("text2"))
    }
}

fun constraintSet() = ConstraintSet {
    val text1 = createRefFor("text1")
    val text2 = createRefFor("text2")
    constrain(text1) {
        top.linkTo(parent.top)
        bottom.linkTo(text2.top)
        start.linkTo(parent.start)
        end.linkTo(parent.end)
    }
    constrain(text2) {
        top.linkTo(text1.bottom)
        bottom.linkTo(parent.bottom)
        start.linkTo(parent.start)
        end.linkTo(parent.end)
    }
}

在这份代码中,通过 ConstraintSet() 函数创建一个约束集,通过 createRefFor 为控件 id 指定一个引用。然后在约束布局中,将这个约束集传入。再给每个控件指定一个 layoutId 就完成了约束条件的解耦。并且使得约束布局可以被复用。