其他分享
首页 > 其他分享> > Compose中的自定义布局(四)

Compose中的自定义布局(四)

作者:互联网

文章目录

一、前言

在实际开发中会出于各种原因进行自定义布局,所以这里简单记录下使用Compose进行自定义布局的方式

二、使用布局修饰符

本段参考代码是google的代码

我们可以使用layout修饰符来元素的测量和布局方式,大概方式如下:

fun Modifier.customLayoutModifier(...) = Modifier.layout { measurable, constraints ->
  ...
})

不过实际应用中通常使用以下写法:

fun Modifier.customLayoutModifier(...) =
    this.layout { measurable, constraints ->
        ...
    })

比如想控制显示的Text顶部到第一行基线的位置,示例如下:

fun Modifier.firstBaselineToTop(
    firstBaselineToTop: Dp
) = layout { measurable, constraints ->
    // Measure the composable
    val placeable = measurable.measure(constraints)

    // 检查是否包含基线,如果不包含则会引发异常
    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)
    }
}

三、创建自定义布局

layout 修饰符仅更改调用可组合项。如需测量和布置多个可组合项,请改用 Layout 可组合项。此可组合项允许您手动测量和布置子项。ColumnRow 等所有较高级别的布局都使用 Layout 可组合项构建而成。大都数自定义布局遵循以下方式:

@Composable
fun MyBasicColumn(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        children = content
    ) { measurables, constraints ->
        // measure and position children given constraints logic here
    }
}

比如我们自定义一个Column布局,示例如下:

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

四、固有特性测量

一般来说,在自定义布局中使用默认测量方式就可以了,但是有时候可能并不能满足需求。因此要指定自定义 Layout 的固有特性测量,则在创建该布局时替换 MeasurePolicy的 minIntrinsicWidthminIntrinsicHeightmaxIntrinsicWidthmaxIntrinsicHeight

代码结构如下:

@Composable
fun MyCustomComposable(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    return object : MeasurePolicy {
        override fun MeasureScope.measure(
            measurables: List<Measurable>,
            constraints: Constraints
        ): MeasureResult {
            // Measure and layout here
        }

        override fun IntrinsicMeasureScope.minIntrinsicWidth(
            measurables: List<IntrinsicMeasurable>,
            height: Int
        ) = {
            // Logic here
        }

        // Other intrinsics related methods have a default value,
        // you can override only the methods that you need.
    }
}

而在创建自定义 layout 修饰符时,替换 LayoutModifier 界面中的相关方法。

fun Modifier.myCustomModifier(/* ... */) = this.then(object : LayoutModifier {

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {
        // Measure and layout here
    }

    override fun IntrinsicMeasureScope.minIntrinsicWidth(
        measurable: IntrinsicMeasurable,
        height: Int
    ): Int = {
        // Logic here
    }

    // Other intrinsics related methods have a default value,
    // you can override only the methods that you need.
})

五、分析修饰符

这里分析下Modifier.padding的原理,代码如下(以下代码源自Google,不过已经修改为更易懂的方式):

// How to create a modifier
@Stable
fun Modifier.padding(all: Dp) =
    this.then(
        PaddingModifier(start = all, top = all, end = all, bottom = all, rtlAware = true)
    )

// Implementation detail
private class PaddingModifier(
    val start: Dp = 0.dp,
    val top: Dp = 0.dp,
    val end: Dp = 0.dp,
    val bottom: Dp = 0.dp,
    val rtlAware: Boolean,
) : LayoutModifier {

    override fun MeasureScope.measure(
        measurable: Measurable,
        constraints: Constraints
    ): MeasureResult {

        val horizontal = start.roundToPx() + end.roundToPx() //获取padding的横向长度
        val vertical = top.roundToPx() + bottom.roundToPx() //获取padding的垂直长度

        // val placeable = measurable.measure(constraints.offset(horizontal, vertical)) //偏移horizontal、vertical距离后进行测量,偏移只会更改内容位置,不会影响测量大小,因为下面已经进行偏移了,所以可以不用这么麻烦
 		val placeable = measurable.measure(constraints)
        val width = constraints.constrainWidth(placeable.width + horizontal)
        val height = constraints.constrainHeight(placeable.height + vertical)
        return layout(width, height) {//定义父布局宽高
            if (rtlAware) {
                placeable.placeRelative(start.roundToPx(), top.roundToPx()) //将组件在现有位置上进行移动,该移动是在布局里面,所以并不会超出布局宽高
            } else {
                placeable.place(start.roundToPx(), top.roundToPx())
            }
        }
    }
}

这里面有一个有意思的问题,就是调用placeable.placeRelative偏移后为什么不会超出设置的宽高?

这里解释下Placeable,文档上解释的意思是:Placeable对应于可以由其父布局定位的子布局。大多数PlaceableMeasurable.measure调用的结果。Placeable不应该在测量调用之间存储。其中placeable.width是父布局所需要留出的宽度,placeable.height是父布局所需要留出的高度。而placeable.measuredWidth才是控件真正的测量宽度,placeable.measuredHeight是控件真正的测量高度。因此调用placeable.placeRelative函数并不会导致组件超出布局。

六、参考链接

  1. Compose中的自定义布局:

    https://developer.android.google.cn/jetpack/compose/layout#decoupled

  2. compose的codelabs

    https://developer.android.google.cn/codelabs/jetpack-compose-layouts#6

  3. Placeable的类说明

    https://developer.android.google.cn/reference/kotlin/androidx/compose/ui/layout/Placeable?hl=en

标签:Compose,layout,自定义,val,布局,fun,Modifier,placeable,constraints
来源: https://blog.csdn.net/Mr_Tony/article/details/118765859