Jetpack Compose 让自定义布局变得简单

371 阅读3分钟

Jtpack Compose 为我们提供了简单的组件,例如ColumnsRows。如果我们想要与它不同的东西怎么办?

例如,我们想要一个自定义布局,以一种排序的方式自动对提供的组件进行布局。示例调用代码如下

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))  
}

结果如下

image.png

我们该怎么做?

使用布局

如果我们看一下可能是最简单的可组合组件,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  
// ...  
}  
}

image.png

措施

在测量期间,我们将获得所有提供的可放置物品。

可能最简单的测量是让我们所有的可放置项目仅继承(父级)给布局的约束,如下所示。

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)  
}

为了显示对最终产品的不同影响,如下所示。

image.png

布局

在我们拥有所有可测量的可放置物品后,我们现在可以相应地布置它们。

最简单的方法是将它们放置在坐标 (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  
}  
}  
}  
}  
}