Jetpack Compose - ConstraintLayout
Compose系列文章,请点原文阅读。原文:是时候学习Compose了!
0、介绍
在xml的时代我们已经了解过了ConstraintLayout的强大功能,比例、相对位置、引导线、屏障、链条等让我们开发页面布局可以大展身手。那么在Compose的时代,ConstraintLayout是如何使用的呢?首先官方给它的介绍很简单:
根据子项之间的约束,定位子项的布局。
但是实际用法上有了很多的差异,接下来一起研究下吧。
1、属性一览
【目前基于alpha08版本的属性】首先请看它的两个函数:
@Composable fun ConstraintLayout(
modifier: Modifier = Modifier,
content: ConstraintLayoutScope.() -> Unit
): Unit
@Composable fun ConstraintLayout(
constraintSet: ConstraintSet,
modifier: Modifier = Modifier,
content: () -> Unit
): Unit
属性参数含义:
参数 | 含义 |
---|---|
modifier: Modifier = Modifier | 应用于布局的修饰符 |
【用法1】content: ConstraintLayoutScope.() -> Unit | 子级内容,可以很方便的创建 约束布局参照物 |
【用法2】constraintSet: ConstraintSet, | 对于子级约束相关描述 |
【用法2】content: () -> Unit | 使用ConstraintSet参数而定义的子级内容 |
2、使用示例
我们先从一些基本概念说起:
2.0、ID
到目前为止我们并没有见到过Compose类似xml中那种给控件命名id的形式,那既然Compose是约束布局,必须要有个控件的编号或者ID,才能实现各种约束条件, 目前在ConstraintLayout中有两种方式来创建这种编号的方式:
如果使用的参数是ConstraintLayoutScope()的方式,那么可以使用如下函数创建编号并应用给控件:Modifier.constrainAs()和createRef()、createRefs()
;
如果使用的参数是ConstraintSet的方式,那么则可以使用如下方式给控件创建ID和然后根据ID创建编号:Modifier.layoutId()和createRefFor()
;
关于这两种创建编号的方法我们就点到这里,下面我们分别在Guideline和Barrier中进行演示;
2.1、Guideline
引导线,可以从特定的位置(某一方向上的偏移量或者某一方向上的比例)创建一条实际并不可见的参考线。总共有如下几种方式:
- createGuidelineFromStart(offset: Dp)
- createGuidelineFromAbsoluteLeft(offset: Dp)
- createGuidelineFromStart(fraction: Float)
- createGuidelineFromAbsoluteLeft(fraction: Float)
- createGuidelineFromEnd(offset: Dp)
- createGuidelineFromAbsoluteRight(offset: Dp)
- createGuidelineFromEnd(fraction: Float)
- createGuidelineFromAbsoluteRight(fraction: Float)
- createGuidelineFromTop(offset: Dp)
- createGuidelineFromTop(fraction: Float)
- createGuidelineFromBottom(offset: Dp)
- createGuidelineFromBottom(fraction: Float)
别被这么多方法吓到,其实就是从上、下、左、右四个方向,分别支持某一偏移量,或者某一比例进行创建引导线。而带有Absolute的表示绝对的左右方向,这里其实就是国际化的问题。
如下代码,我们使用参数为ConstraintLayoutScope()的函数来进行演示:
@Composable
fun ConstraintLayoutDemo() {
ConstraintLayout(
modifier = Modifier.fillMaxSize()
) {
val guideline = createGuidelineFromStart(0.2f)
val (box1, box2) = createRefs()
Box(
modifier = Modifier.fillMaxSize()
.background(color = Color.Yellow)
.constrainAs(box1) {
end.linkTo(guideline)
}
)
Box(
modifier = Modifier.fillMaxSize()
.background(color = Color.Red)
.constrainAs(box2) {
start.linkTo(guideline)
}
)
}
首先ConstraintLayout是一个填充全屏幕的布局,然后在该布局中从开始(左侧)的百分之20的位置,创建一条竖向引导线。
然后创建了两个Box布局,分别是结束部分链接到引导线,开始部分链接到引导线。所以部分效果如下所示:
2.2、Barrier
屏障,这个也容易理解,如下图所示:我们需要填写姓名和身份证信息,由于“姓名”和“身份证”这两个标题长度不一致,但是我们后面填写的信息又要对齐处理。你可能会想到固定前面标题的长度或者限制字符数,这样都没问题,但是更灵活的办法就是使用屏障了。
我们可以在“姓名”和“身份证”这两个标题的右边创建一个屏障,这样你哪个标题长,屏障就会在哪个标题的右边,比如下图中的红线部分,所以后面的内容我们都可以根据屏障进行布局了。
先看下创建屏障的几种函数:
- createStartBarrier()
- createAbsoluteLeftBarrier()
- createTopBarrier()
- createEndBarrier()
- createAbsoluteRightBarrier()
- createBottomBarrier()
一共就四个方向,加上两个绝对方向,同样是国际化的原因。
好了下面一起看下如何使用Barrier结合ID来实现上述的屏障效果。
首先我们在content中先声明了3个Box布局,这三个布局都是只指定了大小和颜色以及他们分别的ID,但是并不知道如何进行排列。
然后在constraintSet中我们根据ID创建了编号,然后对这些编号使用constrain()函数进行约束声明,如果你在xml的时代使用ConstraintLayout布局熟悉的话,你能很快理解这里的写法,就是通过linkTo函数将控件的约束关系描述出来,而且目前的link语法检查也很强大,比如说你指定一个控件的顶部需要对齐到另一个控件的左或者右部,这是不应该的,会直接报错,也就是说top.linkTo(parent.start)
这种类似的都是错误的,原因不用说了吧,代码如下:
@Composable
fun ConstraintLayoutIdDemo() {
ConstraintLayout(
ConstraintSet {
val box1 = createRefFor("box1")
val box2 = createRefFor("box2")
val box3 = createRefFor("box3")
constrain(box1) {
top.linkTo(parent.top)
start.linkTo(parent.start)
}
constrain(box2) {
top.linkTo(box1.bottom)
start.linkTo(parent.start)
}
val barrier = createEndBarrier(box1, box2)
constrain(box3) {
start.linkTo(barrier)
top.linkTo(box1.top)
bottom.linkTo(box2.bottom)
}
}
) {
Box(
modifier = Modifier.layoutId("box1")
.background(color = Color.Red)
.width(100.dp)
.height(100.dp)
)
Box(
modifier = Modifier.layoutId("box2")
.background(color = Color.Yellow)
.width(150.dp)
.height(100.dp)
)
Box(
modifier = Modifier.layoutId("box3")
.background(color = Color.Blue)
.width(200.dp)
.height(100.dp)
)
}
}
实现结果如下所示:
修改布局参数,让红色部分比黄色部分宽50dp,运行结果如下:
2.3、Chain
链,可以参考xml中的chain,大概意思就是将一系列组件按顺序打包成一行或一列。官方将此api标记为了可以改进的状态,猜测可能会被之前提到的Flow布局代替?
先看下api,只有两个,创建横向和竖向的链:
- createHorizontalChain()
- createVerticalChain()
第一个参数是需要打包到一起的控件的编号,第二个属性是链的类型,目前共有三种类型:
- Spread
所有控件平均分布在父布局空间中,是默认的类型 - SpreadInside
第一个和最后一个分布在链条的两端,其余的控件平均分布剩下的空间 - Packed
所有控件打包在一起,并放置在链条的中间
一起看下横向链条的示例代码,我们创建三个色彩不同的Box,然后创建横向链条将这三个Box打包到一起:
@Composable
fun ConstraintLayoutChainDemo() {
ConstraintLayout(modifier = Modifier.fillMaxSize()) {
val (box1, box2, box3) = createRefs()
createHorizontalChain(box1, box2, box3)
Box(modifier = Modifier.size(100.dp).background(Color.Red).constrainAs(box1) {})
Box(modifier = Modifier.size(100.dp).background(Color.Yellow).constrainAs(box2) {})
Box(modifier = Modifier.size(100.dp).background(Color.Blue).constrainAs(box3) {})
}
}
代码渲染结果如下:
当链条上的元素总宽度超出屏幕时,代码渲染结果如下,注意此时是没有滚动效果的,请注意这种情况:
2.4、小结
其实经过上面的代码示例,我们可以发现除了定义ID和编号外,实现约束位置的主要在constrainAs()和constrain()这两个方法中:
我们来看下他们所支持的一些属性:
- parent
- start
- absolutLeft
- top
- end
- absoluteRight
- bottom
- baseline
目前有这么多的属性可以供我们使用,使用linkTo()链接到另一个控件的相应属性上即可,切记一点不能乱用,你不可以让一个控件的左侧对齐另一个控件的上侧,这些IDE都会直接给你报错的。
3、版本更新
暂无
4、未解决问题
目前约束布局的基本的使用已经没有大碍了,更多的是开发过程中的细节问题,遇到后再进行补充。在xml中的根据角度约束的功能目前在compose中还没有发现,敬请期待吧。