范围:三个容器的适用场景、OverflowMode 三档的取舍、EntryRow 的行高与右列宽度、StackPanel 开启滚动后的滚轮与裁剪行为。
前置:快速开始;核心概念 的元素层次与 setter 规则。
容器选型
框架提供三个布局容器,分别对应一类排布需求。StackPanel 将子元素沿一个方向依次排成一行或一列,子元素只需给出尺寸、无需坐标,适用于设置页分组、按钮行与表单列。Canvas 在自己的边界内为子元素指定绝对坐标或九点锚点,适用于浮动徽标、不规则摆位与装饰层。EntryRow 将一行分为左右两列,左侧为标签文字、右侧为控件,适用于任何“名称在左、控件在右“的设置行,行高与右列限宽由容器计算。
选型规则:线性排列用 StackPanel,自由定位用 Canvas,两列设置行用 EntryRow。三者都是 LayoutElement,可以互相嵌套(例如 StackPanel 的一格内放置一条 EntryRow)。容器内部的 measure/arrange 何时触发、如何向上冒泡,见 布局系统。
StackPanel
StackPanel 沿主轴依次摆放可见子元素:主轴方向由 orientation 决定,子元素间距由 spacing 决定,交叉轴上可选择居中。设置页最常见的骨架是可滚动纵向面板,面板内排列两个按钮与一个输入框:
StackPanel vstack = new StackPanel(Orientation.VERTICAL)
.size(300, 400)
.spacing(8)
.bgColor(0x80000000) // 半透明背景;0 表示不画
.overflow(OverflowMode.SCROLL) // 开滚动:裁剪 + 滚轮 + 平滑动画
.scrollbar(true); // 再显示右侧滚动条
vstack.add(new Button("First", 200, 24).font(font));
vstack.add(new Button("Second", 200, 24).font(font));
vstack.add(new TextBox(200, 20).placeholder("Enter text...").font(font));
面板固定为 300×400 并开启滚动,子元素只设置宽高、由面板自上而下排列,间距 8px;文本组件需 .font(font) 才渲染(见 核心概念)。常用属性如下:
| 方法 | 默认值 | 作用 |
|---|---|---|
orientation(Orientation) | 构造时指定 | 主轴方向(VERTICAL / HORIZONTAL) |
direction(StackDirection) | 随 orientation(垂直为 TOP_TO_BOTTOM) | 正向 / 反向排队 |
spacing(int) | 0 | 子元素之间的间距 |
centered(boolean) | false | 子元素在交叉轴上居中 |
autoWidth(boolean) / autoHeight(boolean) | false | 尺寸改为随内容总和 |
bgColor(int) | 0x80000000 | 背景色(0 = 不画) |
scrollbar(boolean) | false | overflow=SCROLL 时显示滚动条 |
滚动与裁剪行为
开启滚动(OverflowMode.SCROLL)后,StackPanel 的行为分为滚轮、裁剪与滚动条三项。滚轮:每格滚动将目标滚动量推进 20 像素(targetScroll ∓ v×20,夹在 0 到可滚上限之间),实际显示位置由 tick() 内的弹簧插值逐渐追上目标,因此滚动呈缓动效果;tick() 中断时滚轮完全不动(Screen 必须在 extractRenderState 里调 uiTickDriver.onRenderFrame(interactionManager),见 快速开始)。裁剪:overflow 为 HIDDEN 或 SCROLL 时容器启用裁剪,将绘制限制在自身边界内,被裁掉的部分既不可见也不可点击;VISIBLE 不裁剪,子元素可以绘制到容器外。滚动条:宽 4px、最短 16px,悬停变亮、可拖拽,仅在 SCROLL 且内容溢出时出现。滚动条是独立的元素对象(ScrollBar),挂载到 children 末位,即本容器最高层;命中查找逆序先扫到它,位于条带下方的内容元素无法截获落在条带上的点击。滚动条不参与内容尺寸与排布,也不随内容滚动,绘制在裁剪区域之外。
Canvas
Canvas 不参与线性排列,每个子元素按坐标或锚点定位,适用于角落徽标、重叠装饰与不规则布局。三种落位方式为指定坐标、锚点居中、锚点加偏移:
Canvas canvas = new Canvas(400, 300);
canvas.add(new Button("Top-Left", 100, 24), 10, 10); // 指定坐标
canvas.add(new Button("Center", 100, 24), Anchor.CENTER); // 锚点居中
canvas.add(new Button("Bottom-Right", 100, 24), Anchor.BOTTOM_RIGHT, -10, -10); // 锚点 + 偏移
第一个子元素固定在 (10,10);第二个按锚点居中;第三个位于右下角并向内偏移 10px。可用锚点共九个:TOP_LEFT、TOP_CENTER、TOP_RIGHT、CENTER_LEFT、CENTER、CENTER_RIGHT、BOTTOM_LEFT、BOTTOM_CENTER、BOTTOM_RIGHT。canvas.show() / canvas.hide() 会递归切换所有子元素的可见性。
EntryRow
EntryRow 是设置页的标准行:先 .left(...) 放置标签,再 .right(...) 放置控件。left / right 在实现上都只是 addChild 调用,列的归属由添加顺序决定:第 1 个子元素为左列、第 2 个为右列,因此必须先 left 后 right,调换顺序会使两列互换。构造“分辨率“设置项:
EntryRow row = new EntryRow(300, 120) // 整行宽 300,左列占位宽 120
.left(new Text("Resolution", 120)) // Text 配方与 font 说明见 [核心概念](developer.CoreConcepts.md)
.right(new SelectDropdown(160)
.options("1920x1080", "2560x1440", "3840x2160")
.selected(0)
.onChanged(i -> changeResolution(i)))
.maxRightWidth(260); // 覆盖右列宽度上限(默认即 260)
左列是一行静态文字(使用核心概念中的 Text,该组件内置字体回退);右列是下拉框;maxRightWidth(260) 与默认值相同,仅作示例;放宽右列上限时传入更大的数。
行高与右列宽度
行高取三者最大:保底 20 像素、左列高度、右列高度,行始终容纳两列中较高者,且不低于 20 像素(普通控件行高)。列间距固定 8 像素(源码常量 GAP)。右列宽度受两道限制:不超过可用宽度(以下公式中的 avail),且不超过 maxRightWidth(默认 260,由 .maxRightWidth(v) 覆盖);算出后右列贴右缘摆放,左右两列均在行内垂直居中。公式如下:
height = max(20, max(lh, rh)) // 行高:保底 20,再取两列较高者
avail = fullWidth - GAP // 可用宽度(GAP = 8;与左栏无关)
w = min(min(rw, avail), maxRightW) // 右列宽:先卡可用宽,再卡上限(默认 260)
lh / rh 是左右两个子元素的高度,rw 是右列的固有宽度。measure / arrange 流水线、坐标与滚动的完整公式体系见 布局系统;修改公式时以该文档为基准同步。
OverflowMode 与滚动
OverflowMode 定义子元素超出容器边界时的处理方式。各档语义如下,行为细节见 StackPanel 的「滚动与裁剪行为」:
stackPanel.overflow(OverflowMode.VISIBLE); // 默认:不裁剪,子元素自由溢出
stackPanel.overflow(OverflowMode.HIDDEN); // 裁剪:溢出部分看不见也点不中
stackPanel.overflow(OverflowMode.SCROLL); // 裁剪 + 滚轮(每格 20px 目标量)+ 平滑滚动 + 可选滚动条
平滑滚动依赖每帧 tick() 推进插值(Screen 在 extractRenderState 里调 uiTickDriver.onRenderFrame(interactionManager),见 快速开始);滚动条样式与拖拽行为属于 StackPanel(4px 宽、最短 16px)。输入如何命中到被裁剪的子元素、命中链的层次见 交互引擎;浮层挡住容器时的输入走向(模态/被动)见 覆盖层与浮动元素。
不变量
- EntryRow 先
.right后.left→ 左右列互换:容器按添加顺序判定列归属,不按方法名。 - EntryRow addChild 超过两个 → 第 3 个子元素不参与排布:measure/arrange 只读索引 0 和 1,该子元素重叠在原点。
- 开了
SCROLL但 Screen 未调用uiTickDriver.onRenderFrame(interactionManager)→ 滚轮完全不动:目标量在变,但显示位置的插值永不推进。 overflow为VISIBLE时不裁剪,子元素可绘制到容器外,溢出部分同样可点击。- 直接给
width/height字段赋值 → 布局不更新:改尺寸必须调用 setter(见 核心概念 的 setter 不变量)。
连带影响
- EntryRow 的
GAP、默认maxRightW、行高保底这些真值一旦调整,公式段与 布局系统 的 EntryRow 段必须同步;该文档给出公式的完整叙述。 - StackPanel 的滚轮步长(20px)、裁剪条件(
HIDDEN | SCROLL)改动 → 「滚动与裁剪行为」与 OverflowMode 部分的数字、条件句需要同步更新。 - OverflowMode 语义改动 → 牵动命中链的裁剪边界判定,交互引擎 的对应描述需一起修改。
- 示例一律使用 核心概念 的
Text;该配方的构造签名变化时,EntryRow 示例需同步更新。