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

相关推荐
fatiaozhang95278 小时前
中兴B860AV5.2-U_原机安卓4.4.2系统专用_晶晨S905L3SB处理器_线刷固件包
android·电视盒子·刷机固件·机顶盒刷机·中兴b860av5.2-u
儿歌八万首8 小时前
Android 自定义 View 实战:打造一个跟随滑动的丝滑指示器
android·kotlin
我有与与症8 小时前
Kuikly 实战:手把手撸一个跨平台 AI 聊天助手 (ChatDemo)
android
恋猫de小郭8 小时前
Flutter UI 设计库解耦重构进度,官方解答未来如何适配
android·前端·flutter
apihz9 小时前
全球IP归属地查询免费API详细指南
android·服务器·网络·网络协议·tcp/ip
hgz07109 小时前
Linux环境下MySQL 5.7安装与配置完全指南
android·adb
Just_Paranoid10 小时前
【Android UI】Android 添加圆角背景和点击效果
android·ui·shape·button·textview·ripple
梁同学与Android10 小时前
Android ---【经验篇】阿里云 CentOS 服务器环境搭建 + SpringBoot项目部署(二)
android·spring boot·后端
风往哪边走10 小时前
自定义简易日历
android