Compose 自定义布局和图形

作者:Best_Jerry日期:2025/10/18

Advanced layout concepts - MAD Skills

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

1. Advanced Layout Concepts

image.png

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

image.png

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

image.png

image.png

2. Layout phase and constraints

for building custom layouts

image.png

image.png

How to enter it

Layout()

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

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

1@Suppress("ComposableLambdaParameterPosition")
2@UiComposable
3@Composable
4inline fun Layout(
5    content: @Composable @UiComposable () -> Unit,
6    modifier: Modifier = Modifier,
7    measurePolicy: MeasurePolicy
8) {
9    ...
10}
11
12@Suppress("NOTHING_TO_INLINE")
13@Composable
14@UiComposable
15inline fun Layout(
16    modifier: Modifier = Modifier,
17    measurePolicy: MeasurePolicy
18) {
19    ...
20}
21
22@Suppress("ComposableLambdaParameterPosition", "NOTHING_TO_INLINE")
23@UiComposable
24@Composable
25inline fun Layout(
26    contents: List<@Composable @UiComposable () -> Unit>,
27    modifier: Modifier = Modifier,
28    measurePolicy: MultiContentMeasurePolicy
29) {
30    ...
31}
32

Measurement and placement for building custom layouts

自定义布局示例

image.png

Modifier.layout()

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

1fun Modifier.layout(
2    measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
3) = this then LayoutElement(measure)
4
5private data class LayoutElement(
6    val measure: MeasureScope.(Measurable, Constraints) -> MeasureResult
7) : ModifierNodeElement<LayoutModifierImpl>() {
8    override fun create() = LayoutModifierImpl(measure)
9
10    override fun update(node: LayoutModifierImpl) {
11        node.measureBlock = measure
12    }
13
14    override fun InspectorInfo.inspectableProperties() {
15        name = "layout"
16        properties["measure"] = measure
17    }
18}
19

Modifier.layout()示例:

1Box(
2    Modifier.background(Color.Gray)
3        .layout { measurable, constraints ->
4            // an example modifier that adds 50 pixels of vertical padding.
5            val padding = 50
6            val placeable = measurable.measure(constraints.offset(vertical = -padding))
7            layout(placeable.width, placeable.height + padding) {
8                placeable.placeRelative(0, padding)
9            }
10        }
11) {
12    Box(Modifier.fillMaxSize().background(Color.DarkGray))
13}
14

Modifier.requiredWidth 源码:

1@Stable
2fun Modifier.requiredWidth(width: Dp) = this.then(
3    SizeElement(
4        minWidth = width,
5        maxWidth = width,
6        enforceIncoming = false,
7        inspectorInfo = debugInspectorInfo {
8            name = "requiredWidth"
9            value = width
10        }
11    )
12)
13

Modifier.requiredWidth示例:

1// The result is a 50.dp x 50.dp magenta box centered in a 100.dp x 100.dp space.
2// Note that although a previous modifier asked it to be 100.dp width, this
3// will not be respected. They would be respected if width was used instead of requiredWidth.
4Box(
5    Modifier
6        .requiredWidth(100.dp)
7        .requiredWidth(50.dp)
8        .aspectRatio(1f)
9        .background(Color.Magenta)
10)
11

image.png

3. Subcompose layout

image.png

image.png

image.png

image.png

BoxWithConstraints

image.png

1@Composable
2@UiComposable
3fun BoxWithConstraints(
4    modifier: Modifier = Modifier,
5    contentAlignment: Alignment = Alignment.TopStart,
6    propagateMinConstraints: Boolean = false,
7    content:
8        @Composable @UiComposable BoxWithConstraintsScope.() -> Unit
9) {
10    val measurePolicy = maybeCachedBoxMeasurePolicy(contentAlignment, propagateMinConstraints)
11    SubcomposeLayout(modifier) { constraints ->
12        val scope = BoxWithConstraintsScopeImpl(this, constraints)
13        val measurables = subcompose(Unit) { scope.content() }
14        with(measurePolicy) { measure(measurables, constraints) }
15    }
16}
17

image.png

image.png

4. Intrinsic measurements

Intrinsic measurements 并不会测量两次。

image.png

如何使用 Intrinsic measurements

image.png

image.png

image.png

1@Stable
2fun Modifier.width(intrinsicSize: IntrinsicSize) = this then IntrinsicWidthElement(
3    width = intrinsicSize,
4    enforceIncoming = true,
5    inspectorInfo = debugInspectorInfo {
6        name = "width"
7        properties["intrinsicSize"] = intrinsicSize
8    }
9)
10
11/**
12 * Intrinsic size used in [width] or [height] which can refer to width or height.
13 */
14enum class IntrinsicSize { Min, Max }
15
16private class IntrinsicWidthElement(
17    val width: IntrinsicSize,
18    val enforceIncoming: Boolean,
19    val inspectorInfo: InspectorInfo.() -> Unit
20) : ModifierNodeElement<IntrinsicWidthNode>() {
21    override fun create() = IntrinsicWidthNode(width, enforceIncoming)
22
23    override fun update(node: IntrinsicWidthNode) {
24        node.width = width
25        node.enforceIncoming = enforceIncoming
26    }
27
28    override fun equals(other: Any?): Boolean {
29        if (this === other) return true
30        val otherModifierElement = other as? IntrinsicWidthElement ?: return false
31        return width == otherModifierElement.width &&
32            enforceIncoming == otherModifierElement.enforceIncoming
33    }
34
35    override fun hashCode() = 31 * width.hashCode() + enforceIncoming.hashCode()
36
37    override fun InspectorInfo.inspectableProperties() {
38        inspectorInfo()
39    }
40}
41
42private class IntrinsicWidthNode(
43    var width: IntrinsicSize,
44    override var enforceIncoming: Boolean
45) : IntrinsicSizeModifier() {
46    override fun MeasureScope.calculateContentConstraints(
47        measurable: Measurable,
48        constraints: Constraints
49    ): Constraints {
50        var measuredWidth = if (width == IntrinsicSize.Min) {
51            measurable.minIntrinsicWidth(constraints.maxHeight)
52        } else {
53            measurable.maxIntrinsicWidth(constraints.maxHeight)
54        }
55        if (measuredWidth < 0) { measuredWidth = 0 }
56        return Constraints.fixedWidth(measuredWidth)
57    }
58
59    override fun IntrinsicMeasureScope.minIntrinsicWidth(
60        measurable: IntrinsicMeasurable,
61        height: Int
62    ) = if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height) else
63        measurable.maxIntrinsicWidth(height)
64
65    override fun IntrinsicMeasureScope.maxIntrinsicWidth(
66        measurable: IntrinsicMeasurable,
67        height: Int
68    ) = if (width == IntrinsicSize.Min) measurable.minIntrinsicWidth(height) else
69        measurable.maxIntrinsicWidth(height)
70}
71

SameWidthBoxes

1// Builds a layout containing three Box having the same width as the widest one.
2//
3// Here width min intrinsic is adding a width premeasurement pass for the
4// Column, whose minimum intrinsic width will correspond to the preferred width of the largest
5// Box. Then width min intrinsic will measure the Column with tight width, the
6// same as the premeasured minimum intrinsic width, which due to fillMaxWidth will force
7// the Box's to use the same width.
8Box {
9    Column(Modifier.width(IntrinsicSize.Min).fillMaxHeight()) {
10        Box(
11            modifier = Modifier.fillMaxWidth()
12                .size(20.dp, 10.dp)
13                .background(Color.Gray)
14        )
15        Box(
16            modifier = Modifier.fillMaxWidth()
17                .size(30.dp, 10.dp)
18                .background(Color.Blue)
19        )
20        Box(
21            modifier = Modifier.fillMaxWidth()
22                .size(10.dp, 10.dp)
23                .background(Color.Magenta)
24        )
25    }
26}
27

SameWidthTextBoxes

1// Builds a layout containing three Text boxes having the same width as the widest one.
2//
3// Here width max intrinsic is adding a width premeasurement pass for the Column,
4// whose maximum intrinsic width will correspond to the preferred width of the largest
5// Box. Then width max intrinsic will measure the Column with tight width, the
6// same as the premeasured maximum intrinsic width, which due to fillMaxWidth modifiers will
7// force the Boxs to use the same width.
8
9Box {
10    Column(Modifier.width(IntrinsicSize.Max).fillMaxHeight()) {
11        Box(Modifier.fillMaxWidth().background(Color.Gray)) {
12            Text("Short text")
13        }
14        Box(Modifier.fillMaxWidth().background(Color.Blue)) {
15            Text("Extremely long text giving the width of its siblings")
16        }
17        Box(Modifier.fillMaxWidth().background(Color.Magenta)) {
18            Text("Medium length text")
19        }
20    }
21}
22

自定义 Intrinsic measurements

image.png

5. Rules in Compose

  1. Order of Compose phases
  2. Single pass measurement

Compose 自定义布局和图形》 是转载文章,点击查看原文


相关推荐


告别加班!这些数组操作技巧让前端开发效率翻倍
良山有风来2025/10/17

你是不是经常遇到这样的场景:产品经理扔过来一堆数据,要你快速处理展示;后端返回的数组结构复杂,需要层层筛选过滤;明明很简单的数据操作,却要写一大堆循环和判断... 别担心!今天这篇干货,就是来拯救你的。我将带你系统掌握JavaScript数组和对象的核心操作,学完立刻就能用在实际项目中。相信我,掌握这些技巧后,你的开发效率至少提升一倍! 数组基础:从创建到遍历 让我们从最基础的数组操作开始。数组就像是一个数据容器,能帮我们有序地存放各种信息。 创建数组有两种常用方式。第一种是用方括号,这是最简洁


阿里云负载均衡SLB的使用参考:创建阿里云ECS实例操作
熙客2025/10/15

目录 一、背景知识 1.1 概念 1.2 负载均衡类型选择 1.3 核心功能与工作原理 1.4 配置负载均衡的注意事项 二、传统型负载均衡CLB的使用示例 2.1 创建3个ECS实例 2.2 安装nginx 2.3 创建负载均衡CLB 2.4 负载均衡配置 2.5 负载均衡检验 一、背景知识 1.1 概念 阿里云负载均衡能将访问流量分发到后端多台云服务器上,提升应用系统的服务能力和高可用性。它主要包含以下三种产品: 特性维度CLB(传统型负载均衡)ALB(应


NineData云原生智能数据管理平台新功能发布|2025年9月版
NineData2025/10/14

本月共发布 17 项更新,其中重点发布 6 项、功能优化 11 项。 重点发布 数据库 DevOps - SQL 任务事务执行 SQL 任务支持以事务方式执行。在 DML 语句执行失败后自动回滚整个任务,确保执行原子性与一致性。 数据库 DevOps - 数据脱敏与敏感扫描 敏感数据保护模块新增支持 PostgreSQL、SQL Server、Oracle 数据源的自动扫描与脱敏,帮助企业更全面地识别并防护敏感信息。 数据库 DevOps - ER 图增强 ER 图现


Flutter - Melos Pub workspaces 实践
LinXunFeng2025/10/13

欢迎关注微信公众号:FSA全栈行动 👋 一、前言 为解决 App 代码臃肿、编译耗时的问题,我们进行了分包重构,核心思路如下: 业务分包:将不同业务线的代码拆分成独立的包,开发者只需聚焦于各自包内的 example 工程进行开发,从而提升编译和运行效率。 功能沉淀:把跨业务复用的功能(包括基础业务和非业务功能)也抽离成独立的包,逐步让主 App 轻量化为一个“空壳”,负责集成所有模块。 依赖管理:业务包之间使用 git 依赖,指向 master 分支;而非业务的功能包则发布到自建的 unp


业务流程建模标准(BPMN)
deepdata_cn2025/10/11

在数字化转型浪潮中,企业对业务流程的可视化、标准化与自动化需求日益迫切。BPMN(Business Process Model and Notation,业务流程建模符号) 作为全球通用的业务流程建模标准,通过统一的图形语言打破了“业务人员说不清楚、IT人员看不懂”的沟通壁垒,成为连接业务需求与技术实现的核心桥梁。 一、BPMN的起源与发展 在BPMN出现前,企业建模缺乏统一规范:有的用流程图(Flowchart),有的用UML活动图,甚至有的用手绘草图——不同角色对同一流程的理解差异巨大,导致


JDK8 新特性 - Stream 流详解
chirrupy_hamal2025/10/9

文章目录 一、认识 Stream二、Stream 的常用方法1、如何获取 Stream 流2、Stream 流常见的中间方法2.3、Stream 流常见的终结方法 一、认识 Stream 二、Stream 的常用方法 1、如何获取 Stream 流 2、Stream 流常见的中间方法 代码简化 s -> s.getName() Studet::getName 代码简化 2.3、Stream 流常见的终结方法 报错


一个基于 ASP.NET Core 的开源、模块化、多租户应用框架和内容管理系统
追逐时光者2025/10/8

前言 今天大姚给大家分享一个基于 ASP.NET Core 的开源、模块化、多租户应用框架和内容管理系统:OrchardCore。 项目介绍 OrchardCore 是一个开源的(BSD-3-Clause license)、模块化的、支持多租户的应用程序框架,使用 ASP.NET Core 构建。同时,它也是一个基于该框架的内容管理系统(CMS)。 DotNetGuide编程学院 DotNetGuide编程学院是一个专注于C#/.NET/.NET Core学习、工作、面试干货和实战教程分享的知识


Python 的 UDP 编程
hubenchang05152025/10/6

#Python 的 UDP 编程 用户数据报协议(User Datagram Protocol) 是一个 无连接、非可靠 的传输层协议,和 TCP 并列,是互联网中最常见的协议之一。 UDP 程序不存在连接,只需要绑定自身地址并收发数据即可。下面是一个示例,它创建了两个 socket,从一个向另一个发送数据。 import socket # 创建 UDP socket sock1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock2


QPS和TPS的区别,在实际项目中,如何准确测量和监控QPS和TPS?
IT橘子皮2025/10/5

QPS(Queries Per Second,每秒查询率)和TPS(Transactions Per Second,每秒事务数)是衡量系统性能的两个关键指标,虽然常被混淆,但存在以下核心区别: 1. ​定义与范围​ ​QPS​:表示服务器每秒能响应的查询次数,通常用于衡量特定查询服务器(如DNS、数据库)的处理能力。例如,一次页面加载可能触发多次查询(如HTML、CSS、JS请求),每个查询均计入QPS。 ​TPS​:表示每秒完成的事务数量。一个事务涵盖客户端请求、服务器内部处理及响应的完整流


豆包怎么部署到本地?一文读懂AI工具的本地化安装全流程
Nightowls__2025/10/4

在数据自主性与即时响应需求的双重推动下,AI 模型的本地部署成为众多企业和个人用户的追求,豆包作为热门 AI 模型,其本地部署也备受关注。豆包本地部署在哪里?又该如何实现高效且安全的本地化运行呢? 一、为什么要本地部署豆包? 在数据自主性与即时响应需求的双重推动下,AI模型的本地部署逐渐成为企业及个人用户优化服务、保障隐私的关键策略。对于豆包这一主流AI模型而言,本地部署具有显著的优势: 满足多元场景需求:不同行业和领域对AI模型的应用需求千差万别,本地部署豆包能够确保模型在复杂网

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0