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

相关推荐
stevenzqzq几秒前
Android Hilt 入门教程_传统写法和Hilt写法的比较
android
wuwu_q5 分钟前
用通俗易懂方式,详细讲讲 Kotlin Flow 中的 map 操作符
android·开发语言·kotlin
_李小白1 小时前
【Android FrameWork】第五天:init加载RC文件
android
2501_916007471 小时前
手机使用过的痕迹能查到吗?完整查询指南与步骤
android·ios·智能手机·小程序·uni-app·iphone·webview
黄毛火烧雪下2 小时前
React Native (RN)项目在web、Android和IOS上运行
android·前端·react native
下位子2 小时前
『OpenGL学习滤镜相机』- Day7: FBO(帧缓冲对象)
android·opengl
從南走到北2 小时前
JAVA国际版同城外卖跑腿团购到店跑腿多合一APP系统源码支持Android+IOS+H5
android·java·ios·微信小程序·小程序
空白格972 小时前
组件化攻略
android
岸芷漫步2 小时前
android框架层弹出对话框的分析
android
Android疑难杂症2 小时前
鸿蒙Media Kit媒体服务开发快速指南
android·harmonyos·音视频开发