我正在参与「启航方案」
作为新时代的 Android 开发者,大概没有不知道 ConstraintLayout
束缚布局的吧?(如果有,并且是你,还不从速去学?)它直接继承自 ViewGroup,用「相对布局」的概念,让布局更灵活、简略。相较于传统的 RelativeLayout
, LinearLayout
等,ConstraintLayout
能做到的扁平化布局的同时,功用还强大很多。关于杂乱的布局,它的优势就更明显了。
ConstraintLayout
依托于 Android Studio 的布局工具,布局变得可视化,不用苦哈哈的写 xml 代码了,在 IDE 上一阵拖拽就搞定。
如此一来,关于 Compose
开发来说,没有 xml 布局了,是不是也没有 ConstraintLayout
了呢?毕竟,官方教程里也只见到 Box
, Column
(相当于 FrameLayout
和 LinearLayout
)这样的简略布局。自己刚开始学 Compose
的时分,的确是这么想的。实际上当然不是啦!要不然接下来也没的讲了。
ConstraintLayout
在 Compose
中也是等同于 Box
相同的官方支撑组件,仅仅同样的,它在杂乱布局中,才能发挥出威力,一般的布局,Box
,Column
或许 Row
随便写写就搞定了,所以官方介绍 Compose
的时分,彻底犯不着一来就上「束缚布局」。
自然地,xml 中的束缚特点,或许说 IDE 的那些拖拽式的束缚特点生成,在Compose
中,就都变到了纯 Kotlin 代码了。
事例实践下
关于 ConstraintLayout
,网上有一堆资料可以学习,在此不细讲了,咱直接上事例,并分别用 View
系统和 Compose
系统来完成。对比之下,关于 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>
作用基本达到:
基本要点如下:
- 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"
})
}
}
因为没有资源图,封面就拿一个颜色充当了。调用 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 里面对应的尺度和样式,非常简略。
作用:
简介
简介就更简略了,就一个标题、一个介绍,共两个文本:
// ...
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
,意思是「文本占满容器宽度」。
至此,完成。来看看最终作用:
不说和 View 的版别一模相同,最少也能说是如出一辙吧?
小结
通过这个直观的事例,信任 Compose
下的 ConstraintLayout
可以放心地放入你的工程了。一路运用下来,是不是很简略?当然这里仅仅一个简略的比如,当布局愈加杂乱时,可能还会引进更多的知识点,比如说 Guide
、Barrier
这些东西,有机会再持续聊吧。