04 · 布局¶
分工¶
| 负责方 | 管什么 |
|---|---|
| Style | 本盒 width/height、min/max、padding、margin、overflow |
| 布局容器 | 子节点如何排列:方向、gap、对齐、flex 增长、滚动 |
不要试图用 Style 表达「兄弟间距」或「flex grow」——那些在 Row / Column 等方法上。
常用容器¶
Box¶
单子节点外壳;可带 Key、点击、Style:
下文示例统一假定已通过 activeTheme := core.ActiveTheme(ctx) 取得当前主题值,
颜色直接引用主题的 Colors 字段(主题是值,不是查询——见 architecture)。
layout.Box(
text.New("内容"),
).Style(style.New().Padding(16).Background(style.Solid(activeTheme.Colors.Surface)))
可交互:
layout.Box(text.New("点我")).
Key("tap").
OnClick(func() { send(Tapped{}) }).
Style(style.New().Padding(8).Cursor(style.CursorPointer))
Row / Column¶
layout.Column(
text.New("标题").Size(20),
layout.Row(
button.New("A").OnClick(...),
button.New("B").OnClick(...),
).Gap(8),
).Gap(12)
常用方法:
Gap(n)— 子项间距AlignStart/AlignMiddle/AlignEnd等 — 交叉轴对齐Width/Height/FillWidth/FillHeight/MaxWidth/Padding— 容器尺寸与内边距;Padding接受 1 个全边值、2 个纵横值、3 个上/横/下值或 4 个上右下左值- 配合
Flexible/Expanded分配剩余空间(参见本章约束说明)
Center¶
在可用空间中居中子树:
Scroll¶
内容超出时滚动:
Scroll 只在内容溢出时预留滚动条槽位。页面高度会变化时(例如设置页切换
分类),使用覆盖式滚动条,避免短页面接在长页面后面时内容宽度跳动:
layout.Scroll(content).Overlay()
// 只隐藏滚动条,不禁用滚轮或触摸滚动,也不预留槽位。
layout.Scroll(content).WithoutScrollbar()
聊天或日志等持续追加内容的列表可用 .FollowEnd():初始停在末尾;用户滚离末尾后
暂停跟随,回到底部后恢复。
滚动视口统一使用 layout.Scroll;原 layout.Scrollbar 已删除。
根外壳和滚动条部件可通过 .Style(...) 调整,滚动条使用 PartTrack / PartThumb。
重复调用 .Style(...) 按顺序合并。
视觉外扩与裁剪¶
layout.Scroll、layout.List 和 splitpane.New 会自动为 Style 的阴影和
轮廓在视口内部预留空间;Input、Card 等使用公共 Box 外壳的组件无需额外
添加 margin。安全空间只出现在视口边缘,不会被加到每两个列表项之间。
显式的 OverflowHidden 仍是本地裁剪边界;已被该边界裁掉的子内容不会再影响
外层视口。
自绘超出盒子边界的内容可通过 style.VisualOutset 声明最小范围,或在没有 Style
外壳时使用 layout.VisualOutset:
layout.Box(customCanvas).Style(
style.New().VisualOutset(6, 8, 10, 8),
)
// 直接自绘时:top, right, bottom, left。
layout.VisualOutset(ctx, gtx, customCanvas, 6, 8, 10, 8)
首次发现新的外扩范围时,视口会请求下一帧完成安全边距布局。它不会让这类 绘制越过滚动视口或窗口边界。
Grid / Wrap / Stack 与视觉组件¶
| 容器 | 用途 |
|---|---|
Grid |
网格 |
Wrap |
自动换行流式排列 |
Stack |
叠放 |
splitpane.New |
可拖分割条,来自 widget/splitpane |
divider.New |
视觉分隔线,来自 widget/divider |
Surface / Card |
带主题表面/卡片语义的容器 |
布局组合可参考仓库 examples/showcase,在仓库根目录运行 go run ./examples/showcase。
尺寸怎么设¶
尺寸写在 Style 上,再交给 Box 或控件的 .Style(...):
layout.Box(
input.New(m.Name).OnChange(...),
).Style(style.New().Width(320))
// 或
layout.Box(child).Style(
style.New().Width(200).
Height(40).
MinWidth(120).
MaxWidth(400).
Padding(12).
Margin(4),
)
宽高比:
约束直觉¶
父级给出 最大可用空间;子级在约束内声明自己的尺寸与排列。 Fill / 百分比类能力通过 Style 与容器策略配合(详见规格 03,应用侧记「父约束 → 子声明」即可)。
典型页面骨架¶
func View(_ *ui.Context, m Model, send ui.Send[Msg]) ui.Widget {
return layout.Column(
titlebar.NewPlatform(...), // 或自定义顶栏
layout.Row(
sidebar.New(...),
layout.Expanded(
layout.Scroll(layout.Column(
// 主内容
).Gap(16).Style(style.New().Padding(24))),
),
),
statusbar.New(...),
)
}
(具体 API 以组件文档与示例为准;骨架思想是「列/行嵌套 + Scroll + Style 内边距」。)
注意¶
- 每帧重建 Widget 树是正常的;不要缓存可变 Widget 状态。
- 有状态控件必须有稳定 Key。
- 列表项 Key 用业务 id,不要用仅与渲染顺序绑定的临时下标(若顺序会变)。