Compose 自定义布局和图形

Advanced layout concepts - MAD Skills

Compose 提供各种开箱即用型解决方案,可帮助您快速轻松地从头开始构建界面。但是,如果您需要更进一步,以实现完全自定义的界面,该怎么办?详细了解高级布局概念,以便自行构建自定义布局,让您的设计实现更上一层楼。

1. Advanced Layout Concepts

Layout 这一术语在 Compose 中有多种含义:

以下是每种含义的相关解释:

2. Layout phase and constraints

for building custom layouts

How to enter it

Layout()

调用 Layout() 可组合项是布局阶段和构建自定义布局的起点。

Layout() 目前有三个重载方法:

kotlin 复制代码
@Suppress("ComposableLambdaParameterPosition")
@UiComposable
@Composable
inline fun Layout(
    content: @Composable @UiComposable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    ...
}

@Suppress("NOTHING_TO_INLINE")
@Composable
@UiComposable
inline fun Layout(
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
) {
    ...
}

@Suppress("ComposableLambdaParameterPosition", "NOTHING_TO_INLINE")
@UiComposable
@Composable
inline fun Layout(
    contents: List<@Composable @UiComposable () -> Unit>,
    modifier: Modifier = Modifier,
    measurePolicy: MultiContentMeasurePolicy
) {
    ...
}

Measurement and placement for building custom layouts

自定义布局示例

Modifier.layout()

如果只想对某一个 Composable 自定义布局,使用 Modifier.layout() 更为方便。

kotlin 复制代码
fun Modifier.layout(
    measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) = this then LayoutElement(measure)

private data class LayoutElement(
    val measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
) : ModifierNodeElement<LayoutModifierImpl>() {
    override fun create() = LayoutModifierImpl(measure)

    override fun update(node: LayoutModifierImpl) {
        node.measureBlock = measure
    }

    override fun InspectorInfo.inspectableProperties() {
        name = "layout"
        properties["measure"] = measure
    }
}

Modifier.layout()示例:

kotlin 复制代码
Box(
    Modifier.background(Color.Gray)
        .layout { measurable, constraints ->
            // an example modifier that adds 50 pixels of vertical padding.
            val padding = 50
            val placeable = measurable.measure(constraints.offset(vertical = -padding))
            layout(placeable.width, placeable.height + padding) {
                placeable.placeRelative(0, padding)
            }
        }
) {
    Box(Modifier.fillMaxSize().background(Color.DarkGray))
}

Modifier.requiredWidth 源码:

kotlin 复制代码
@Stable
fun Modifier.requiredWidth(width: Dp) = this.then(
    SizeElement(
        minWidth = width,
        maxWidth = width,
        enforceIncoming = false,
        inspectorInfo = debugInspectorInfo {
            name = "requiredWidth"
            value = width
        }
    )
)

Modifier.requiredWidth示例:

kotlin 复制代码
// The result is a 50.dp x 50.dp magenta box centered in a 100.dp x 100.dp space.
// Note that although a previous modifier asked it to be 100.dp width, this
// will not be respected. They would be respected if width was used instead of requiredWidth.
Box(
    Modifier
        .requiredWidth(100.dp)
        .requiredWidth(50.dp)
        .aspectRatio(1f)
        .background(Color.Magenta)
)

3. Subcompose layout

BoxWithConstraints

kotlin 复制代码
@Composable
@UiComposable
fun BoxWithConstraints(
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    propagateMinConstraints: Boolean = false,
    content:
        @Composable @UiComposable BoxWithConstraintsScope.() -> Unit
) {
    val measurePolicy = maybeCachedBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
    SubcomposeLayout(modifier) { constraints ->
        val scope = BoxWithConstraintsScopeImpl(this, constraints)
        val measurables = subcompose(Unit) { scope.content() }
        with(measurePolicy) { measure(measurables, constraints) }
    }
}

4. Intrinsic measurements

Intrinsic measurements 并不会测量两次。

如何使用 Intrinsic measurements

kotlin 复制代码
@Stable
fun Modifier.width(intrinsicSize: IntrinsicSize) = this then IntrinsicWidthElement(
    width = intrinsicSize,
    enforceIncoming = true,
    inspectorInfo = debugInspectorInfo {
        name = "width"
        properties["intrinsicSize"] = intrinsicSize
    }
)

/**
 * Intrinsic size used in [width] or [height] which can refer to width or height.
 */
enum class IntrinsicSize { Min, Max }

private class IntrinsicWidthElement(
    val width: IntrinsicSize,
    val enforceIncoming: Boolean,
    val inspectorInfo: InspectorInfo.() -> Unit
) : ModifierNodeElement<IntrinsicWidthNode>() {
    override fun create() = IntrinsicWidthNode(width, enforceIncoming)

    override fun update(node: IntrinsicWidthNode) {
        node.width = width
        node.enforceIncoming = enforceIncoming
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        val otherModifierElement = other as? IntrinsicWidthElement ?: return false
        return width == otherModifierElement.width &&
            enforceIncoming == otherModifierElement.enforceIncoming
    }

    override fun hashCode() = 31 * width.hashCode() + enforceIncoming.hashCode()

    override fun InspectorInfo.inspectableProperties() {
        inspectorInfo()
    }
}

private class IntrinsicWidthNode(
    var width: IntrinsicSize,
    override var enforceIncoming: Boolean
) : IntrinsicSizeModifier() {
    override fun MeasureScope.calculateContentConstraints(
        measurable: Measurable,
        constraints: Constraints
    ): Constraints {
        var measuredWidth = if (width == IntrinsicSize.Min) {
            measurable.minIntrinsicWidth(constraints.maxHeight)
        } else {
            measurable.maxIntrinsicWidth(constraints.maxHeight)
        }
        if (measuredWidth < 0) { measuredWidth = 0 }
        return Constraints.fixedWidth(measuredWidth)
    }

    override fun IntrinsicMeasureScope.minIntrinsicWidth(
        measurable: IntrinsicMeasurable,
        height: Int
    ) = if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height) else
        measurable.maxIntrinsicWidth(height)

    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
        measurable: IntrinsicMeasurable,
        height: Int
    ) = if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height) else
        measurable.maxIntrinsicWidth(height)
}

SameWidthBoxes

kotlin 复制代码
// Builds a layout containing three Box having the same width as the widest one.
//
// Here width min intrinsic is adding a width premeasurement pass for the
// Column, whose minimum intrinsic width will correspond to the preferred width of the largest
// Box. Then width min intrinsic will measure the Column with tight width, the
// same as the premeasured minimum intrinsic width, which due to fillMaxWidth will force
// the Box's to use the same width.
Box {
    Column(Modifier.width(IntrinsicSize.Min).fillMaxHeight()) {
        Box(
            modifier = Modifier.fillMaxWidth()
                .size(20.dp, 10.dp)
                .background(Color.Gray)
        )
        Box(
            modifier = Modifier.fillMaxWidth()
                .size(30.dp, 10.dp)
                .background(Color.Blue)
        )
        Box(
            modifier = Modifier.fillMaxWidth()
                .size(10.dp, 10.dp)
                .background(Color.Magenta)
        )
    }
}

SameWidthTextBoxes

kotlin 复制代码
// Builds a layout containing three Text boxes having the same width as the widest one.
//
// Here width max intrinsic is adding a width premeasurement pass for the Column,
// whose maximum intrinsic width will correspond to the preferred width of the largest
// Box. Then width max intrinsic will measure the Column with tight width, the
// same as the premeasured maximum intrinsic width, which due to fillMaxWidth modifiers will
// force the Boxs to use the same width.

Box {
    Column(Modifier.width(IntrinsicSize.Max).fillMaxHeight()) {
        Box(Modifier.fillMaxWidth().background(Color.Gray)) {
            Text("Short text")
        }
        Box(Modifier.fillMaxWidth().background(Color.Blue)) {
            Text("Extremely long text giving the width of its siblings")
        }
        Box(Modifier.fillMaxWidth().background(Color.Magenta)) {
            Text("Medium length text")
        }
    }
}

自定义 Intrinsic measurements

5. Rules in Compose

  1. Order of Compose phases

  2. Single pass measurement

相关推荐
爱笑鱼2 小时前
Binder(八):远端进程死了,BinderProxy 为什么还能收到通知?
android
Android-Flutter2 小时前
Kotlin 冷流与热流详解
android·kotlin
zhangphil3 小时前
Android ContentProvider/ContentResolver读图片,跨进程 IPC慢
android
yueqc13 小时前
Android .so 文件压缩
android·so·包体积
龚礼鹏3 小时前
深入解析 Android Automotive (AAOS) 启动流程与 CarService 核心机制(基于 Android 16 最新源码视角)
android
rosmis3 小时前
agent各指标定义
android·java·开发语言
风样滴男人哟5 小时前
PHP特性之反射类ReflectionClass机制
android·开发语言·php
小孔龙6 小时前
RenderNode 与 DisplayList:Android 硬件加速的绘制记录与复用
android·性能优化
qizayaoshuap6 小时前
鸿蒙 ArkTS 实战:搜索列表 城市实时过滤(示例 94)
android·华为·harmonyos
朝星6 小时前
Framework筑基之Handler消息机制5(应用层)
android