Android compose自定义布局

 开新坑了,compose自定义布局。基础知识不说了,直接上正题。

我们知道,在views体系下,自定义布局需要view集成viewgroup重写onMeasure、onLayout方法,在compse中,是使用Layout的compose方法,结构如下:

以一个自定义Column为例:

1、首先我们定义自己的cpmpose函数

@Composablefun 
MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
)
复制代码

这个方法包含最基础的两个入参,一个是修饰符modifier,一个是@composable注解的lamda表达式作为子项的内容

2、看看具体函数体的操作

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit) {
    Layout(
        modifier = modifier,
        content = content
    ) { measurables, constraints ->
        // Don't constrain child views further, measure them with given constraints
        // List of measured children
        val placeables = measurables.map { measurable ->
            // Measure each children
            measurable.measure(constraints)
        }
        // Set the size of the layout as big as it can
        layout(constraints.maxWidth, constraints.maxHeight) {
            // Track the y co-ord we have placed children up to
            var yPosition = 0
            // Place children in the parent layout
            placeables.forEach { placeable ->
                // Position item on the screen
                placeable.placeRelative(x = 0, y = yPosition)
                // Record the y co-ord placed up to
                yPosition += placeable.height
            }
        }
    }
}
复制代码

代码是从官网抄的,正确性就不用说了,具体分析一下作用。

1、在函数体中使用已经定义好的Layout方法。(这个有点类似 类 的继承,compose中所有组件定义都是使用方法,没有类中的子类父类的概念,如果想要做一些统一的封装操作会比较麻烦,可以使用这种方法,函数体内去执行另一个封装好的函数,而函数最后一个参数使用@composable注解的lamda)Layout方法把修饰符和content接收,回调中发送的是 measurables和constraints.从名字就可以猜出这两个参数的作用

  • measurables:可测量元素,就是传进来的子元素
  • constraints:父类约束条件

Layout函数的lamda来自于第三个入参MeasurePolicy,这是一个接口,上面两个参数就来自于这个接口的回调:

fun MeasureScope.measure(
    measurables: List<Measurable>,
    constraints: Constraints
): MeasureResult
复制代码

2、使用map函数遍历measurables,每一个measurable调用measure并把constraint传入,相当于给每个子控件根据父控件的约束进行测量,类似于views体系下面的measure,得到palceables。

3、调用layout方法(注意小写,这是单独放置一个控件的方法,是单个可组合项的修饰,具体下面会再讲),layout(width,heigiht)传入布局的宽和高,在layout方法lamda中,对每一个placeable调用place方法(有几个类似的,这里使用placeRelative),传入相应坐标,完成子view的布局

到这里一个自定义布局就完成了,其实和views体系下面很像,也是相似的两步:

1、测量每个子view在父view约束下的大小

2、遍历子view,使用layout方法将每个view放在正确的位置上。

大同小异大同小异

3、关于layout(注意是小写的)

先抄一段官网的说明:

您可以使用 layout 修饰符来修改元素的测量和布局方式,layout 是一个 lambda;它的参数包括您可以测量的元素(以 measurable 的形式传递)以及该可组合项的传入约束条件(以 constraints 的形式传递)

再抄一段代码:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp) =
 layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)
    // Check the composable has a first baseline
    check(placeable[FirstBaseline] != AlignmentLine.Unspecified)
    val firstBaseline = placeable[FirstBaseline]
    // Height of the composable with padding - first baseline
    val placeableY = firstBaselineToTop.roundToPx() - firstBaseline
    val height = placeable.height + placeableY
    layout(placeable.width, height) {
        // Where the composable gets placed
        placeable.placeRelative(0, placeableY)
    }}
复制代码

这个是官网上面修改text baseline top padding的方法。具体内容就不说了,可以看到,是定义了一个modifier的扩展函数,回调参数是一个measurable,内部具体还是调用layout,大同小异大同小异。

到这里自定义布局就结束了,自定义布局难点主要还是在子view在父view约束下面的布局逻辑,也可以看到其实这个移植以前views下面的自定义布局应该是比较容易的,把坐标计算逻辑抽离,然后就可以轻松完成移植了。(另外我从这里还发现了compose下面怎么实现类似以前类的继承,那就是活用fun中最后一个lamda参数,由于kotlin语法的关系,容易把lamda看作是函数体,其实在fun中只是调用了一个fun,函数具体执行都被隐藏了起来)

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享