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

相关推荐
杨筱毅4 小时前
【Android】【底层机制】组件生命周期以及背后的状态管理
android·底层机制
Jeled6 小时前
Kotlin 实现社交 App 音视频模块:语音录制、播放、暂停与进度控制全流程封装
android·kotlin·android studio·音视频
沐怡旸6 小时前
【底层机制】【Android】Binder架构与原理
android·面试
Jeled6 小时前
Jetpack —> Media3的分析和使用
android
木易士心8 小时前
Android setContentView源码与原理分析
android
00后程序员张8 小时前
iOS混淆与IPA文件加固全流程实战 防止苹果应用被反编译的工程级方案
android·ios·小程序·https·uni-app·iphone·webview
用户41659673693559 小时前
Jetpack Compose 进阶:实现列表嵌套悬停(LazyColumn & HorizontalPager)
android
2501_9151063210 小时前
iOS 应用加固与苹果软件混淆指南,如何防止 IPA 被反编译与二次打包?
android·ios·小程序·https·uni-app·iphone·webview
huibin14785236911 小时前
不能识别adb/usb口记录
android·adb