我正在参与「启航方案」

作为新时代的 Android 开发者,大概没有不知道 ConstraintLayout 束缚布局的吧?(如果有,并且是你,还不从速去学?)它直接继承自 ViewGroup,用「相对布局」的概念,让布局更灵活、简略。相较于传统的 RelativeLayoutLinearLayout 等,ConstraintLayout 能做到的扁平化布局的同时,功用还强大很多。关于杂乱的布局,它的优势就更明显了。

ConstraintLayout 依托于 Android Studio 的布局工具,布局变得可视化,不用苦哈哈的写 xml 代码了,在 IDE 上一阵拖拽就搞定。

如此一来,关于 Compose 开发来说,没有 xml 布局了,是不是也没有 ConstraintLayout 了呢?毕竟,官方教程里也只见到 Box, Column(相当于 FrameLayoutLinearLayout)这样的简略布局。自己刚开始学 Compose 的时分,的确是这么想的。实际上当然不是啦!要不然接下来也没的讲了。

ConstraintLayoutCompose 中也是等同于 Box 相同的官方支撑组件,仅仅同样的,它在杂乱布局中,才能发挥出威力,一般的布局,BoxColumn 或许 Row 随便写写就搞定了,所以官方介绍 Compose 的时分,彻底犯不着一来就上「束缚布局」。

自然地,xml 中的束缚特点,或许说 IDE 的那些拖拽式的束缚特点生成,在Compose 中,就都变到了纯 Kotlin 代码了。

事例实践下

关于 ConstraintLayout,网上有一堆资料可以学习,在此不细讲了,咱直接上事例,并分别用 View 系统和 Compose 系统来完成。对比之下,关于 ComposeConstraintLayout 用法,大概心里就有谱了。

事例如下:

Compose:  怎么能少了 ConstraintLayout

这是一款英语阅览 APP百词斩爱阅览」中的一个界面,是一个专辑的概况页

咱们就完成顶部那块概况信息部分吧。

View

xml 布局打天下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/cover"
        android:layout_width="90dp"
        android:layout_height="120dp"
        android:layout_marginStart="24dp"
        android:layout_marginTop="24dp"
        android:scaleType="centerCrop"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:srcCompat="@tools:sample/avatars" />
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="6dp"
        android:text="迷你人物传"
        android:textColor="#000"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintStart_toEndOf="@+id/cover"
        app:layout_constraintTop_toTopOf="@+id/cover" />
    <TextView
        android:id="@+id/title_en"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:text="Mini Biography"
        android:textColor="#000"
        android:textSize="14sp"
        app:layout_constraintStart_toStartOf="@+id/title"
        app:layout_constraintTop_toBottomOf="@+id/title" />
    <TextView
        android:id="@+id/require"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:text="词汇量要求"
        android:textColor="#8000"
        android:textSize="14sp"
        app:layout_constraintStart_toStartOf="@+id/title"
        app:layout_constraintTop_toBottomOf="@+id/title_en" />
    <TextView
        android:id="@+id/require_level"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="12dp"
        android:fontFamily="sans-serif-medium"
        android:text="大学+"
        android:textColor="#000"
        android:textSize="14sp"
        app:layout_constraintStart_toEndOf="@+id/require"
        app:layout_constraintTop_toTopOf="@+id/require" />
    <TextView
        android:id="@+id/summary"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="6dp"
        android:fontFamily="sans-serif-medium"
        android:text="共 7 篇短文"
        android:textColor="#8000"
        android:textSize="14sp"
        app:layout_constraintStart_toStartOf="@+id/title"
        app:layout_constraintTop_toBottomOf="@+id/require" />
    <TextView
        android:id="@+id/profile_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="32dp"
        android:fontFamily="sans-serif-medium"
        android:text="简介"
        android:textColor="#000"
        android:textSize="16sp"
        app:layout_constraintStart_toStartOf="@+id/cover"
        app:layout_constraintTop_toBottomOf="@+id/cover" />
    <TextView
        android:id="@+id/profile_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:layout_marginEnd="24dp"
        android:text="迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传"
        android:textColor="#000"
        android:textSize="16sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/profile_title"
        app:layout_constraintTop_toBottomOf="@+id/profile_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

作用基本达到:

Compose:  怎么能少了 ConstraintLayout

基本要点如下:

  • cover 封面定下了根底位置
  • 标题依靠 cover 放好,下面的英文标题、等级信息等,就跟着它排好就行
  • 简介也依靠 cover,起始对齐,距离必定距离

要是用线性布局或许其他的,不知道要写多久,嵌套多少层,而上面的代码,纯是在 Android Studio 的「Design」模式下,拖拽组件库生成的,方便又简略,这便是 ConstraintLayout 的强大。

Compose

Compose 里运用 ConstraintLayout,需求引进如下包,根底库里是没有的:

implementation "androidx.constraintlayout:constraintlayout-compose:$version"

好,准备就绪,开干!

import androidx.constraintlayout.compose.ConstraintLayout
@Composable  
    private fun DetailInfo() {  
    ConstraintLayout {  
    }  
}  

Box 的运用没什么两样,而这个 androidx.constraintlayout.compose.ConstraintLayout 是这样的:

@Composable
inline fun ConstraintLayout(
    modifier: Modifier = Modifier,
    optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
    crossinline content: @Composable ConstraintLayoutScope.() -> Unit
) {
//...
}

参数真少,怎么完成束缚布局的杂乱功用的?答案就在这个 ConstraintLayoutScope 里。持续边做边讲吧。

封面

首要,还是先摆好封面:

@Composable
@Preview(showBackground = true, backgroundColor = 0xffffff)
private fun DetailInfo() {
    ConstraintLayout {
        val (cover) = createRefs()
        Box(modifier = Modifier
            .size(90.dp, 120.dp)
            .background(Color.Gray)
            .constrainAs(cover) {
                start.linkTo(parent.start, 24.dp) // 相当于 layout_constraintStart_toStartOf="parent" + layout_marginStart="24dp"
                top.linkTo(parent.top, 24.dp) // 相当于 layout_constraintTop_toTopOf="parent" + layout_marginTop="24dp"
            })
    }
}

Compose:  怎么能少了 ConstraintLayout

因为没有资源图,封面就拿一个颜色充当了。调用 createRefs(),是为了创立「引证索引」,其实便是类似 id,这是创立束缚关联的钥匙 —— 后面的 constrainAs 方法需求这个参数。

前面提到的 ConstraintLayoutScope 该上场了:

@LayoutScopeMarker
class ConstraintLayoutScope @PublishedApi internal constructor() : ConstraintLayoutBaseScope() {
    // ...
    // 用于创立多个 ConstrainedLayoutReferences
    @Stable
    fun createRefs() =
            referencesObject ?: ConstrainedLayoutReferences().also { referencesObject = it }
    //...
    @Stable
    fun Modifier.constrainAs(
        ref: ConstrainedLayoutReference,
        constrainBlock: ConstrainScope.() -> Unit
    ) = this.then(ConstrainAsModifier(ref, constrainBlock))
    //...
}

createRefs()Modifier.constrainAs() 都是它内部界说的方法。

而真实的束缚关联的树立,便是在 createRefs() 方法跟随的 ConstrainScope 域块了,详见注释。

信息列

信息列,便是封面右侧那一列文本信息。

封面的布局搞明白了,其他的也就水到渠成:

@Composable
@Preview(showBackground = true, backgroundColor = 0xffffff)
private fun DetailInfo() {
    ConstraintLayout(Modifier.fillMaxSize()) {
        val (cover, title, titleEn, require, requireLevel, summary) = createRefs()
        Box(modifier = Modifier
            .size(90.dp, 120.dp)
            .background(Color.Gray)
            .constrainAs(cover) {
                start.linkTo(parent.start, 24.dp)
                top.linkTo(parent.top, 24.dp)
            })
        Text(text = "迷你人物传", fontWeight = FontWeight.Bold, fontSize = 18.sp, color = Color.Black, modifier = Modifier.constrainAs(title) {
            top.linkTo(cover.top, 6.dp)
            start.linkTo(cover.end, 16.dp)
        })
        Text(text = "Mini Biography", fontSize = 14.sp, color = Color.Black, modifier = Modifier.constrainAs(titleEn) {
            top.linkTo(title.bottom, 6.dp)
            start.linkTo(title.start)
        })
        Text(text = "词汇量要求", fontSize = 14.sp, color = Color.Black.copy(.5f), modifier = Modifier.constrainAs(require) {
            top.linkTo(titleEn.bottom, 6.dp)
            start.linkTo(title.start)
        })
        Text(text = "大学+", fontSize = 14.sp, color = Color.Black, fontWeight = FontWeight.Medium, modifier = Modifier.constrainAs(requireLevel) {
            top.linkTo(require.top)
            start.linkTo(require.end, 12.dp)
        })
        Text(text = "共 7 篇短文", fontSize = 14.sp, color = Color.Black.copy(.5f), modifier = Modifier.constrainAs(summary) {
            top.linkTo(require.bottom, 6.dp)
            start.linkTo(title.start)
        })
    }
} 

createRefs() 的结果里,又加了多个「id」,分别是对应各个文本项。依葫芦画瓢,各个文本组件彻底复制了 xml 里面对应的尺度和样式,非常简略。

作用:

Compose:  怎么能少了 ConstraintLayout

简介

简介就更简略了,就一个标题、一个介绍,共两个文本:

// ...
val (cover, title, titleEn, require, requireLevel, summary, profileTitle, profileContent) = createRefs()
// ...
t = "简介", fontSize = 16.sp, color = Color.Black, modifier = Modifier.constrainAs(profileTitle) {
    top.linkTo(cover.bottom, 32.dp)
    start.linkTo(cover.start)
})
Text(text = "迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传迷你人物传", fontSize = 16.sp, color = Color.Black, fontWeight = FontWeight.Medium, modifier = Modifier.constrainAs(profileContent) {
    top.linkTo(profileTitle.bottom, 12.dp)
    start.linkTo(cover.start)
    end.linkTo(parent.end, 24.dp)
    width = Dimension.fillToConstraints
})

值得注意的是,介绍文本的束缚里,有一个 width 的设定,值为 Dimension.fillToConstraints。这其实便是 View 版别的 MATCH_CONSTRAINT,意思是「文本占满容器宽度」。

至此,完成。来看看最终作用:

Compose:  怎么能少了 ConstraintLayout

不说和 View 的版别一模相同,最少也能说是如出一辙吧?

小结

通过这个直观的事例,信任 Compose 下的 ConstraintLayout 可以放心地放入你的工程了。一路运用下来,是不是很简略?当然这里仅仅一个简略的比如,当布局愈加杂乱时,可能还会引进更多的知识点,比如说 GuideBarrier 这些东西,有机会再持续聊吧。