Jetpack Compose布局(三) - 自定义布局
在上篇《Jetpack Compose技术快速上手》一文中简单介绍了Compose,那么这边我们就来学习下Compose的布局。由于布局这块涉及内容较多,会分开写。
布局主要包括:布局基础知识、Material组件和布局、自定义布局、Compose中使用ConstraintLayout。
自定义布局涉及的知识点:
Compose布局的过程
在 Compose 中,界面元素由可组合函数表示,此类函数在被调用后会发出一部分界面,这部分界面随后会被添加并呈现在屏幕上的界面树中。每个界面元素都有一个父元素,还可能有多个子元素。此外,每个元素在其父元素中都有一个位置,指定为 (x, y) 位置;也都有一个尺寸,指定为 width 和 height。
父元素定义其子元素的约束条件。元素需要在这些约束条件内定义尺寸。约束条件可限制元素的最小和最大 width 和 height。如果某个元素有子元素,它可能会测量每个子元素,以帮助确定其尺寸。一旦某个元素确定并报告了它自己的尺寸,就有机会定义如何相对于自身放置它的子元素。
在界面树中布置每个节点的过程分为三个步骤:
1.测量所有子项
2.确定自己的尺寸
3.放置其子项
★注意:Compose 界面不允许多遍测量。这意味着,布局元素不能为了尝试不同的测量配置而多次测量任何子元素。
扩展布局修饰符 layout
使用 layout
修饰符来修改元素的测量和布局方式。Layout 是一个 lambda;它的参数包括您可以测量的元素(以 measurable 的形式传递)以及该可组合项的传入约束条件(以 constraints 的形式传递)。自定义布局修饰符可能如下所示:
fun Modifier.customLayoutModifier(...) =
this.layout { measurable, constraints ->
...
})
下面以Text
的顶部内边距为例,假如有这样的需求:以第一行文字基线为参考设置内边距,如下图示:
文字位置Y = 设置高度 - 文字高度
代码如下:
/**
* 1.自定义布局修饰符-修改文字基线距离顶部距离
*/
fun Modifier.firstBaselineToTop(firstBaselineToTop: Dp)=layout { measurable, constraints ->
//先测量
val placeable = measurable.measure(constraints)
check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
val firstBaseLine = placeable[FirstBaseline]
//文字位置Y
val placeableY = firstBaselineToTop.roundToPx() - firstBaseLine
val height = placeable.height + placeableY
//重新布局
layout(placeable.width,height){
placeable.placeRelative(0,placeableY)
}
}
最终效果与pading效果进行对比,如下图:
上图是paddingtofirstbaseline,下图是paddingTop
创建自定义布局 Layout
在Android View系统中如果想自定义布局,则必须继承ViewGroup 并实现测量和布局函数,而在Compose中自定义布局则简单很多,可直接使用Layout
可组合项来实现,Layout
允许手动测量和布置子项。Column
和Row
就是由Layout
构建而成的。
这里要注意与自定义修饰符中的layout
区分。
我们以自定义一个垂直布局为例(类似Column
),代码如下:
/**
* 2.自定义Layout实现组件垂直布局
*/
@Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content:@Composable ()->Unit
){
//步骤一:
//其中measurables,constraints为Layout最后一个参数的lambda写法
Layout(content = content, modifier = modifier){ measurables,constraints ->
//步骤二:通过给点的约束条件constraints对组件进行测量
val placeables = measurables.map {
it.measure(constraints)
}
//获取所有元素的高度总和
val wrapHeight = placeables.sumOf {
it.height
}
//步骤三:布局,设置可允许的布局大小
layout(constraints.maxWidth,wrapHeight){
var yPosition = 0
//步骤四:设置每个组件的位置
placeables.forEach {placeable->
//设置组件的x,y坐标
placeable.placeRelative(x = 0,y=yPosition)
//计算下一个组件的y坐标
yPosition += placeable.height
}
}
}
}
值得注意的是:Layout
与 layout
修饰符类似,但第一个参数measurables
是需要测量的子项的列表,而 constraints 是来自父项的约束条件。
示例效果如下:
另外这里自定义了一个瀑布流布局,有兴趣的可以看下。
效果图:
布局方向
如果需要更改布局的方向,可以使用LocalLayoutDirection
实现。支持修改的布局方向:Ltr(由左到右)
、Rtl(由右到左)
。
如下例:
/**
* 3.布局方向
*/
@Composable
fun ChangeColumnDirection(){
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl ) {
Column(Modifier.fillMaxWidth()) {
Text("Title")
Text("Subtitle")
}
}
}
示例效果:
image.png
到这里自定义布局就介绍完了,内容相对较少。
欢迎留言,一起学习,共同进步!