Jtpack Compose 为我们提供了简单的组件,例如Columns和Rows。如果我们想要与它不同的东西怎么办?
例如,我们想要一个自定义布局,以一种排序的方式自动对提供的组件进行布局。示例调用代码如下
CustomLayout(Modifier.size(150.dp).border(1.dp, Color.Green)) {
Text("Hello There")
Text("Good")
Text("Longer please")
Text("More Text")
Box(Modifier.height(10.dp).width(100.dp).background(Color.Cyan))
Box(Modifier.height(10.dp).width(10.dp).background(Color.Red))
Box(Modifier.height(10.dp).width(50.dp).background(Color.Magenta))
}
结果如下
我们该怎么做?
使用布局
如果我们看一下可能是最简单的可组合组件,Box我们可以在下面看到,它只是在使用Layout组件。
@Composable
inline fun Box(
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit
) {
val measurePolicy = rememberBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
Layout(
content = { BoxScopeInstance.content() },
measurePolicy = measurePolicy,
modifier = modifier
)
}
所以要构建我们的自定义组件
@Composable
fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Measure
// ....
// Layout
// ...
}
}
措施
在测量期间,我们将获得所有提供的可放置物品。
可能最简单的测量是让我们所有的可放置项目仅继承(父级)给布局的约束,如下所示。
val placaebles = measurables.map { measurable ->
measurable.measure(constraints = constraints)
}
如果我们有多个项目不想占据整个布局约束(特别是考虑到布局具有固定的刚性约束大小),这并不理想。
更好的方法是通过将 minWidth 和 minHeight 设置为 0 来使每个可放置的项目更加灵活
val looseConstraints = constraints.copy(
minWidth = 0 ,
minHeight = 0 ,
)
val placaebles = measurables.map { measurable ->
measurable.measure(constraints = looseConstraints)
}
为了显示对最终产品的不同影响,如下所示。
布局
在我们拥有所有可测量的可放置物品后,我们现在可以相应地布置它们。
最简单的方法是将它们放置在坐标 (0, 0) 中,布局的大小使用父级最大约束
layout(constraints.maxWidth, constraints.maxHeight) {
placaebles.forEach { placeable ->
placeable.place( 0 , 0 )
}
}
布局宽度和高度
constraint.maxWidth以更实际的方式,我们可能想要确定我们的布局的宽度和高度是否应该由父级的约束(即和constraint.maxHeight)或基于其可放置的项目来确定。
下面的代码显示了如何检查我们是否应该使用父级约束、可放置项目的最大宽度和总高度。
val hasBoundedWidth = constraints.hasBoundedWidth
val hasFixedWidth = constraints.hasFixedWidth
val hasBoundedHeight = constraints.hasBoundedHeight
val hasFixedHeight = constraints.hasFixedHeight
val width =
if (hasBoundedWidth && hasFixedWidth) constraints.maxWidth
else placeablesWidth.maxOf { it.width }.coerceAtMost(constraints.maxWidth)
val height =
if (hasBoundedHeight && hasFixedHeight) constraints.maxHeight
else placeablesWidth.sumOf { it.height }.coerceAtMost(constraints.maxHeight)
代码片段聊天的一个很好的参考来自这里。
在我们的代码示例中,我们只是简单地取
layout(constraints.maxWidth, constraints.maxHeight)
有序放置
现在让我们看看如何放置每个项目。这就是有趣的地方,我们可以在这里进行复杂的放置或简单的放置。
在我们的例子中,因为我们想根据它们的宽度来放置它们,所以让我们按如下方式对它们进行排序。
val placaebles = measurables.map { measurable ->
measurable.measure(constraints = looseConstraints)
}.sortedBy { it.width }
完成后,我们现在可以轻松放置它们,我们保留 X 并相应地增加 Y
layout(constraints.maxWidth, constraints.maxHeight) {
var y = 0
placaebles.forEach { placeable ->
placeable.place( 0 , y)
y += placeable.height
}
}
就是这样!
完整代码
你可以在这里得到设计。但为了便于参考,您可以在下面获得完整的源代码
class AutoWidthSortColumnActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
CustomLayoutTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting()
}
}
}
}
@Composable
fun Greeting(modifier: Modifier = Modifier) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
CustomLayout(Modifier.size(150.dp).border(1.dp, Color.Green)) {
Text("Hello There")
Text("Good")
Text("Longer please")
Text("More Text")
Box(Modifier.height(10.dp).width(100.dp).background(Color.Cyan))
Box(Modifier.height(10.dp).width(10.dp).background(Color.Red))
Box(Modifier.height(10.dp).width(50.dp).background(Color.Magenta))
}
}
}
@Composable
fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
val looseConstraints = constraints.copy(
minWidth = 0,
minHeight = 0,
)
val placaebles = measurables.map { measurable ->
measurable.measure(constraints = looseConstraints)
}.sortedBy { it.width }
layout(constraints.maxWidth, constraints.maxHeight) {
var y = 0
placaebles.forEach { placeable ->
placeable.place(0, y)
y += placeable.height
}
}
}
}
}