最近在给一个跑了七八年的 JavaGUI 桌面工具做界面翻新,打开老代码,满屏都是RowLayout和RowData。很多人一上手 SWT 就直接奔着GridLayout去了,觉得RowLayout太简陋——说白了就是"从左往右摆一排控件"。可真正把窗口拖窄、把控件动态藏起来、把高 DPI 缩放打开之后,你会发现RowLayout那套按行排列、自动换行、逐控件覆写的机制,反而是工具栏、标签云、按钮条这类场景里最省事的选择。这篇就聊透 SWT 里这个被低估的布局管理器:它到底怎么算尺寸、wrap 和 pack 到底在争什么、RowData里那几个字段分别管哪条轴、ScrolledComposite 里换行为什么不生效、以及我踩过的那些让人想砸键盘的坑。不管你是刚接触 SWT 的学生,还是维护老 RCP 界面的老手,这里的内容应该都能直接抄去用。
1. RowLayout 到底是个什么东西,什么时候该用它
1.1 它的本质:一条会折行的"传送带"
RowLayout的模型非常朴素:把子控件一个接一个地排在一行里,排不下就换行。你可以把它想成超市收银台上的商品传送带——东西从左边进,摆满了就往下一层走。它不关心你摆的是什么,只关心"这一行还剩多宽"。默认情况下它水平排列、允许换行、每个控件保持自己的首选尺寸、外侧留 3 像素边距、控件之间留 3 像素间距。这几个默认值加起来,就是很多人第一次跑 SWT 程序时那股"按钮挤在一起、四周还有点空"的观感来源。
和FillLayout那种"所有子控件平分容器"的粗暴策略不同,RowLayout尊重控件自己computeSize()出来的结果。你放一个宽 200 的Text,它就占 200;你放三个宽度不一的Button,三个宽度就各不相同。这一点在工具栏场景里极其关键——"保存"和"另存为"两个按钮本来就该宽度不同,硬要平分反而难看。
它的第二个特点是"能折"。wrap打开之后,容器变窄,控件自动滚到下一行。这个能力在FillLayout里是没有的,在GridLayout里要靠numColumns手动算列数也做不自然。做标签列表、做筛选条件栏、做那种"窗口无论多宽都不出现横向滚动条"的界面,RowLayout的折行是最实测下来最省代码的方案。
1.2 和 GridLayout、FillLayout 放一起怎么选
很多人纠结的不是怎么用,而是"到底该选哪个"。我把这三个布局在真实项目里的取舍整理成一张表,你可以直接对照自己的场景:
| 维度 | RowLayout | GridLayout | FillLayout |
|---|---|---|---|
| 排列逻辑 | 顺序排一行,超宽折行 | 严格二维网格,行列数可控 | 水平或垂直平铺 |
| 是否尊重首选尺寸 | 是(pack=true时) | 是 | 否,强制平分 |
| 折行/自动换列 | 原生支持 | 不支持 | 不支持 |
| 单控件单独定制 | 通过RowData覆写宽高 | 通过GridData覆写grab/span/对齐 | 无 |
| 复杂表单能力 | 弱 | 强 | 无 |
| 典型场景 | 工具栏、按钮条、标签云、状态栏 | 表单、设置页、多列面板 | 整体分栏、主区域填充 |
| 学习成本 | 低 | 中高 | 极低 |
我自己的判断标准很粗暴:如果这个容器里的控件"顺序排开就行、宽度参差也没关系、窄了就换行",用RowLayout;如果对不齐就活该被产品经理骂,那就上GridLayout;如果只是把一个区域整体撑满,FillLayout一行代码搞定。
举两个具体例子。一个是搜索条件栏:Label + Text + Label + Combo + Button依次排列,窗口拉窄时自动折成两行——RowLayout十行代码,GridLayout得算半天numColumns,还得处理折行时不出现的空位。另一个是主内容区:左边一个树,右边一个表格——这是FillLayout(SWT.HORIZONTAL)或者GridLayout的活儿,RowLayout在这里会给你惊喜(惊悚的那种),因为两个控件都会保持首选宽度,容器一拉大,右边直接空出一大块白。
1.3 什么时候千万别用它
说句实在的,RowLayout在一些场景下是纯纯的坑。第一种是"严格对齐的表单"。RowLayout没有列的概念,你把 5 个Label放在左边、5 个Text放在右边,只要字号一变、或者某个标签文字长了两个字符,整排就歪了。这种必须用GridLayout配GridData.horizontalAlignment = SWT.FILL,别硬扛。
第二种是"需要按比例分配空间"的布局。RowLayout里没有grabExcessHorizontalSpace这种概念,唯一能拉伸的是fill和justify,而这两个是"填满整行"和"把剩余空隙切成等份塞进间隔里",跟"让中间这个输入框占 70% 宽度"完全是两回事。要做自适应比例,老老实实换GridLayout。
第三种是"单个容器里控件超过几百个"。RowLayout每次 layout 都要遍历所有子控件重新计算折行,控件数量上百之后,窗口拖拽会明显掉帧。我做标签云的时候贴了 400 多个Label,窗口一拖就一顿一顿的,最后改成虚拟化 + 分批加载才顺过来。十几二十个控件随便用,上百个就得掂量掂量。
注意:判断该不该用 RowLayout,看一条就够——"这些控件是否彼此独立、没有对齐诉求"。有对齐诉求的一律上 GridLayout,别指望 RowLayout 能对齐。
2. 六个参数决定一切:wrap、pack、fill、justify、spacing、margin
2.1 wrap 与 pack:一个管"换不换行",一个管"占多大"
wrap是RowLayout最有辨识度的开关,默认true。打开时,当剩余宽度不足以放下下一个控件,它就把这个控件挪到下一行。关掉之后,超出容器的控件会被直接裁掉——注意是"裁掉",不是"出现滚动条",RowLayout本身不管滚动,想要滚动条得起ScrolledComposite。
pack这个名字容易误解,它不是"打包",而是"这一行是否只占最小所需空间"。默认true,意思是每行的高度等于这一行里最高控件的首选高度,行与行之间紧凑排列。设成false之后,行会沿着"排列方向的垂直方向"扩展,把容器的剩余空间分掉。举个直观的例子:容器高 200,里面两行按钮,每行高 26。pack=true时,两行加起来占 52 像素,剩下的 148 全是空白,全都堆在底部;pack=false时,两行的行高会被拉成各 100,按钮在 100 高的行里垂直居中——视觉效果就是"按钮被均匀撒在整个容器里"。
这两个参数的组合有四种,实际行为差得挺远:
| wrap | pack | 实际效果 | 适用场景 |
|---|---|---|---|
| true | true | 紧凑折行,右下留白 | 工具栏、标签云(最常用) |
| true | false | 折行,且行高均分容器高度 | 卡片流、纵向均匀分布 |
| false | true | 不折行,超出被裁剪 | 单行固定工具条 |
| false | false | 不折行,行高撑满 | 少见的定高横幅 |
我个人 90% 的场景都是wrap=true, pack=true,剩下 10% 里大部分是pack=false做纵向均匀分布。wrap=false我基本只在"确定容器宽度够用"的单行状态栏里用,因为一旦用户把窗口拉窄,界面直接残废,风险太高。
2.2 fill 与 justify:按钮变成"面条"的两大元凶
这两个参数是新手翻车率最高的地方。fill默认false,一旦设成true,控件会在"垂直于排列方向"的轴上被拉满。水平排列时,就是把每个控件的高度拉到该行的行高。注意,它拉的是高度,不是宽度——很多同学开了fill发现按钮变高了却没变宽,以为是没生效,其实是理解错了轴。
justify默认false,设成true时,RowLayout会把容器里"这一行没用完的剩余宽度"平均分配到控件之间的间距上,并且让第一个控件贴左边、最后一个贴右边。这玩意儿看起来很美,实际特别挑场景:如果一行只有 2 个按钮、容器宽 800,两个按钮会被推到两端,中间空出巨大的缝隙,视觉上像布局崩了。它真正合适的场景是"导航条首尾各一个元素",比如左边是标题、右边是关闭按钮。
这里有个很多人不知道的细节:fill和justify同时打开时,justify会先把剩余宽度分掉,fill再在剩下的空间里干活,两者叠加出来的效果非常诡异。我建议这两个开关永远只开一个,除非你已经在真机上把窗口从 300 拖到 1920 全看了一遍。
还有一个隐藏得比较深的坑:fill=true时,行高由该行最高控件决定,而"最高控件"可能是你放进去的一个带SWT.BORDER的Text(它有内边距,算出来比按钮高 2~4 像素)。结果就是同一行的按钮全被拉高到Text的高度,看着像"按钮被压扁后又拉长"。解决办法是给那个Text显式套一个RowData定死高度。
2.3 spacing 与 margin 家族:像素账要算清楚
spacing管的是控件与控件之间的距离,marginLeft / marginRight / marginTop / marginBottom管的是控件与容器边框的距离,两者互不干扰、会叠加。默认值都是 3,这个 3 在低分辨率屏幕上勉强能看,在高 DPI 屏上会显得特别局促,所以我一般统一调成 6~8。
这里有个历史遗留问题:老代码里常见marginWidth和marginHeight,这两个字段在较新的 SWT 版本里已经废弃,它们会映射成marginLeft + marginRight和marginTop + marginBottom。也就是说你写layout.marginWidth = 10,实际效果是左右各 10。新代码别再用了,直接写四个方向,语义清楚,也不会因为版本差异行为不一致。
具体怎么估一个工具栏的宽度?拿我上周做的一个筛选条举例:8 个按钮,实测平均宽度 92 像素,spacing = 6,左右margin各 12。那么单行总宽 = 12 + 92×8 + 6×7 + 12 = 790。此时如果外壳宽度是 600,可用宽度 600 - 24 = 576,每个"按钮+间距"占 98,576 ÷ 98 ≈ 5.8,也就是第一行能放 5 个,剩下 3 个折到第二行。这个估算不需要精确,但能让你在写代码前就知道"窗口默认尺寸该给多少",省掉反复试的功夫。
高度同理:单行时容器内容高 = 按钮高 +marginTop+marginBottom。按钮高 26、上下 margin 各 12,就是 50。如果外面还套了GridLayout的单元格,记得把 50 这个数填进GridData.heightHint,否则会被别的控件挤变形。
提示:改完 margin 之后一定要调
shell.layout(true, true),true的第二个参数表示"连同子容器一起重排",只调layout()无参版本,嵌套容器里的布局可能不刷新。
3. RowData:把单个控件从"集体宿舍"里拎出来
3.1 三个字段怎么用
RowLayout给每个控件统一规则,RowData给单个控件开后门。它只有三个字段:width、height、exclude。width和height一旦设了非默认值(SWT.DEFAULT),就会覆盖控件自己算出来的尺寸。这一点特别重要:RowData的优先级高于computeSize(),所以你可以用它强行把一个按钮压成 60 像素宽,也可以把一个Label撑到 200 像素高,让它和旁边的多行文本对齐。
exclude是最被低估的一个。设成true之后,这个控件依然显示,但不参与布局计算——它不占位置,后面的控件会补上来。这跟setVisible(false)完全不是一回事:setVisible(false)只是看不见,位置还留着,界面会出现一个诡异的空洞。所以动态显示/隐藏控件在RowLayout里的标准姿势是"两个都做":
Button extra = new Button(shell, SWT.PUSH); extra.setText("高级设置"); RowData rd = new RowData(); rd.width = 96; rd.height = 26; rd.exclude = true; // 初始不占位 extra.setLayoutData(rd); extra.setVisible(false); // 初始不可见 // 某个开关触发时 boolean show = advanced.getSelection(); extra.setVisible(show); rd.exclude = !show; // 改同一个 RowData 实例即可 shell.layout(true, true);注意这里我改的是同一个RowData实例,而不是new一个新的塞回去。原因在下一节。
3.2 为什么不能每次 new 一个 RowData
这是我在老代码里见过最多的错误写法:
// 错误示范 button.setLayoutData(new RowData(96, 26)); // 后来某处又写 button.setLayoutData(new RowData()); // 想把尺寸恢复默认第二行确实把尺寸恢复了,但同时也把之前所有通过RowData设置的属性(包括exclude)全部冲掉了。更麻烦的是,RowLayout内部在计算时会去读每个控件的layoutData,如果类型不是RowData(比如你手滑塞了个GridData进去),运行时会直接抛ClassCastException,异常信息还特别隐晦,只告诉你类转换失败,不告诉你是哪个容器惹的祸。
我的习惯是:一个控件的RowData只创建一次,之后所有修改都改字段。动态场景下我会把它存成字段或者用Map<Control, RowData>缓存起来:
Map<Control, RowData> dataCache = new HashMap<>(); RowData rd = dataCache.computeIfAbsent(ctrl, c -> { RowData d = new RowData(); d.width = 96; d.height = 26; return d; }); rd.exclude = !visible; ctrl.setVisible(visible);多写三行,省下两小时排查。
3.3 混合控件时的垂直对齐
RowLayout有个让人抓狂的行为:它不做垂直对齐。一行里有高有矮的控件,它们全部贴着行的顶部排。你放一个 26 像素高的按钮和一个 60 像素高的多行文本框,按钮会顶在最上面,下面空出 34 像素,看起来像布局没做完。
解决办法只有两个。第一,用RowData把矮的控件高度设成和高的那个一致(或者用SWT.CENTER样式在某些控件上产生居中效果,但支持得很有限)。第二,更稳妥的做法是——把这一行拆成一个子Composite,子容器用GridLayout做垂直居中,外层RowLayout只负责排列这个子容器。听起来绕,但真写过几次之后就习惯了,RowLayout嵌套GridLayout是很常见的组合拳。
还有一个反直觉的点:fill=true时控件会被拉到行高,但如果行里某个控件设了RowData.height,行高会按"最大值"算,而不是按你设的那个值算。所以给一个控件设高,等于同时给整行所有开了fill的控件设高。这个联动关系经常被忽略,导致"我只改了一个控件的高度,怎么整排都变了"。
4. 从零手搓一个能自适应的筛选工具条
4.1 需求与骨架
假设要做一个这样的东西:顶部一排筛选控件(关键词输入、类别下拉、日期区间、几个快捷按钮),窗口拉窄时自动折行,窗口拉宽时排成一行,底部右侧还要有一个对齐到右边的"重置"按钮。这个需求用RowLayout做,代码量大概 60 行。
先搭骨架。SWT 程序的固定套路是Display→Shell→ 设置布局 → 添加控件 →open()→ 事件循环:
public class FilterBarDemo { public static void main(String[] args) { Display display = new Display(); Shell shell = new Shell(display); shell.setText("筛选工具条 - RowLayout 示例"); shell.setSize(720, 180); RowLayout layout = new RowLayout(); layout.wrap = true; layout.pack = true; layout.fill = true; layout.justify = false; layout.spacing = 6; layout.marginLeft = 12; layout.marginRight = 12; layout.marginTop = 12; layout.marginBottom = 12; shell.setLayout(layout); buildContent(shell); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) { display.sleep(); } } display.dispose(); } }这段的关键在fill = true:开关打开之后,同一行里所有控件的高度会被统一,按钮和输入框看起来才齐。justify = false是刻意的,因为我要在最后手动控制"重置"按钮的位置,用justify会把它和前面的控件全部拉开,反而乱。
4.2 控件构建与尺寸覆写
接下来往里填控件。这里我把每个控件的RowData都显式设一遍,图的就是行为确定:
private static void buildContent(Shell shell) { Label lblKey = new Label(shell, SWT.NONE); lblKey.setText("关键词:"); lblKey.setLayoutData(size(SWT.DEFAULT, 26)); Text txtKey = new Text(shell, SWT.BORDER); txtKey.setLayoutData(size(180, 26)); Label lblCat = new Label(shell, SWT.NONE); lblCat.setText("类别:"); lblCat.setLayoutData(size(SWT.DEFAULT, 26)); Combo cmbCat = new Combo(shell, SWT.READ_ONLY); cmbCat.setItems(new String[]{"全部", "订单", "退款", "对账"}); cmbCat.select(0); cmbCat.setLayoutData(size(110, 26)); Label lblRange = new Label(shell, SWT.NONE); lblRange.setText("日期:"); lblRange.setLayoutData(size(SWT.DEFAULT, 26)); DateTime dtFrom = new DateTime(shell, SWT.DATE | SWT.DROP_DOWN); dtFrom.setLayoutData(size(130, 26)); Label dash = new Label(shell, SWT.NONE); dash.setText("至"); dash.setLayoutData(size(SWT.DEFAULT, 26)); DateTime dtTo = new DateTime(shell, SWT.DATE | SWT.DROP_DOWN); dtTo.setLayoutData(size(130, 26)); Button btnQuery = new Button(shell, SWT.PUSH); btnQuery.setText("查询"); btnQuery.setLayoutData(size(80, 26)); Button btnReset = new Button(shell, SWT.PUSH); btnReset.setText("重置"); btnReset.setLayoutData(size(80, 26)); } private static RowData size(int w, int h) { RowData rd = new RowData(); rd.width = w; rd.height = h; return rd; }这里有个细节值得单独说:RowData.width = SWT.DEFAULT和width = 0完全不是一回事。SWT.DEFAULT(值是 -1)表示"用控件自己算出来的宽度",0表示"真的给它 0 像素宽"——控件会被压成一条线,肉眼几乎看不见,但布局里还留着spacing的缝隙,排查起来极其迷惑。我见过有人误写成 0,然后花一晚上找"为什么按钮消失了"。
另外DateTime控件在不同平台上的首选宽度差异很大(macOS 和 Windows 风格不一样),所以必须显式定宽,否则折行位置会不一致。这就是为什么我把所有控件的尺寸都写死——在RowLayout里,确定性的尺寸比"让它自己算"更重要,因为你无法预判computeSize()在不同主题下会返回什么。
4.3 折行行为实测与窗口尺寸设定
代码跑起来之后,我做了几组实测,记录如下(按钮平均宽 80,spacing = 6,左右 margin 各 12):
| 外壳宽度 | 可用宽度 | 单行容纳 | 实际行数 | 观察 |
|---|---|---|---|---|
| 320 | 296 | 约 2 个控件 | 6 行以上 | 严重折行,建议设最小宽度 |
| 480 | 456 | 约 4 个控件 | 4 行 | 可用,但显得挤 |
| 640 | 616 | 约 6 个 | 2~3 行 | 比较舒服 |
| 720 | 696 | 约 7 个 | 2 行 | 默认尺寸,推荐 |
| 1024 | 1000 | 全部 10 个 | 1 行 | 一行排完,右侧留白 |
从表里能看出来,RowLayout的折行是"逐控件贪心"的,不做全局平衡。也就是说可能出现第一行满满当当、第二行只有两个控件的"孤儿行"。如果你介意这个,只能自己算好宽度,或者干脆接受它——毕竟工具栏这种场景,用户对孤儿行宽容度很高。
还有一件事必须做:给Shell设最小尺寸。RowLayout不会阻止你把它拖到 100 像素宽,那时候每个控件都折成单独一行,界面上会竖着排一长串,很难看。
shell.setMinimumSize(480, 140);这一行代码能省掉 80% 的"用户把窗口拖成一条线然后来投诉"的问题。setMinimumSize在部分老版本平台上对Shell生效有限制,稳妥一点的做法是在SWT.Resize里判断客户端宽高,小于阈值就强行setSize回去。
5. 常见问题与排查实录
5.1 问题速查表
把我在项目里遇到过的问题整理成表,遇到症状直接查:
| 症状 | 大概率原因 | 处理办法 |
|---|---|---|
| 布局完全不生效,控件挤在左上角 | 忘记parent.setLayout(...)或没调layout() | 设布局后调用shell.layout(true, true) |
运行时报ClassCastException | 父容器用RowLayout,子控件塞了GridData | 检查所有setLayoutData的类型 |
| 控件隐藏后留下空洞 | 只调了setVisible(false),没设exclude | RowData.exclude = true并重排 |
| 按钮被拉成面条 | justify = true或fill = true误开 | 关掉重试,逐个排除 |
| 折行不生效 | wrap = false,或父容器宽度未定 | 检查wrap,父容器给确定宽度 |
| ScrolledComposite 内不折行 | 内容Composite宽度没跟随视口 | 监听getClientArea变化重设setMinSize |
| 高 DPI 屏下间距过小 | 固定像素值未按缩放系数换算 | 用display.getDPI()做缩放 |
| 控件顺序错乱 | 多个线程操作 UI | 所有 UI 操作切回Display线程 |
修改RowData后界面不变 | 只改了对象没触发重排 | 改完调layout(true, true) |
表里最后一条值得强调:RowData是个普通 POJO,改它的字段不会自动触发任何事件。RowLayout只在 layout 的时候才去读这些值。所以"我明明把exclude设成true了,怎么没反应"——八成是忘了重排。
5.2 ScrolledComposite 里折行失效的完整解法
这个坑我踩过两次,第二次才彻底搞明白。现象是:ScrolledComposite里放一个RowLayout(wrap=true)的内容容器,窗口拉窄,内容容器不会跟着变窄,控件永远排一行,然后出现横向滚动条。
根本原因是:ScrolledComposite里的内容Composite需要自己知道自己该多宽。它默认会按computeSize(SWT.DEFAULT, SWT.DEFAULT)算出一个"一条线排到底"的宽度,这个宽度远超视口,于是ScrolledComposite就给你横向滚动条,而不是让内容变窄折行。
正确姿势是在视口尺寸变化时,把内容宽度重新设给内容容器:
ScrolledComposite sc = new ScrolledComposite(parent, SWT.V_SCROLL); sc.setExpandHorizontal(true); sc.setExpandVertical(false); Composite content = new Composite(sc, SWT.NONE); RowLayout rl = new RowLayout(); rl.wrap = true; rl.spacing = 8; rl.marginLeft = 8; rl.marginRight = 8; content.setLayout(rl); // 填充内容 for (int i = 0; i < 60; i++) { Label tag = new Label(content, SWT.BORDER); tag.setText("标签项 " + i); tag.setLayoutData(new RowData(110, 28)); } sc.setContent(content); // 关键:跟随视口宽度重算 Runnable reflow = () -> { int w = sc.getClientArea().width; if (w <= 0) return; content.setSize(content.computeSize(w, SWT.DEFAULT)); sc.setMinSize(content.computeSize(w, SWT.DEFAULT)); }; sc.addListener(SWT.Resize, e -> reflow.run()); sc.getVerticalBar().addListener(SWT.Selection, e -> reflow.run()); reflow.run();要点有三个。第一,setExpandHorizontal(true)让内容跟着视口水平扩展。第二,content.setSize(...)传的宽度必须是视口宽度,而不是SWT.DEFAULT。第三,加SWT.Resize监听,否则窗口缩放之后不会重新折行。这三条少一条,表现都不一样:少第一条内容不跟着宽,少第二条永远排一行,少第三条缩放后不刷新。
注意:
computeSize的第二个参数传SWT.DEFAULT,让高度按内容自适应。如果你硬编码高度,滚动条的拇指大小会算错,滚到一半就卡住。
5.3 几个我自己攒下来的小技巧
第一,别在RowLayout里直接放ToolBar。ToolBar有自己的布局逻辑(SWT.FLAT、SWT.WRAP样式),塞进RowLayout里会和一个普通控件一样只占"首选宽度",但它的内部项目不会跟着折行,看起来半死不活。工具栏就用ToolBar+ToolItem,RowLayout留给"按钮条"这种用Button拼的场景。
第二,改spacing的时候顺手看一眼marginWidth。老代码里这两个经常被一起写,marginWidth的废弃会有编译警告,但不会报错,运行时行为也会变(映射成左右各一半),很容易出现"我明明设了 10,怎么看起来只有 5"的错觉。
第三,做一个dumpLayout的调试方法。在排查布局问题时,把容器里所有控件的getBounds()打印出来,一眼就能看出哪个控件占了不该占的宽度:
private static void dumpLayout(Composite c) { for (Control ctrl : c.getChildren()) { System.out.printf("%-16s %s%n", ctrl.getClass().getSimpleName(), ctrl.getBounds()); } }getBounds()打印出来如果是(0, 0, 0, 0),说明这个控件压根没参与布局,去查exclude;如果宽度远远超出你的预期,去查RowData.width是不是被别的代码覆盖了;如果高度异常,八成是fill和某个高控件联动导致的。
第四,垂直排列的RowLayout(SWT.VERTICAL)别配fill=true,除非你想让每个按钮都横跨整个侧边栏。垂直模式下fill拉的是宽度,侧边栏宽度 200,按钮就会被拉成 200 宽,看起来像一排扁扁的色块。真要做侧边导航,宁可给按钮设RowData.width,把fill关掉,再配合RowData(height)统一高度。
第五,高 DPI 缩放要自己做。RowLayout的spacing、margin、RowData里的宽高全是原始像素,不会自动跟着系统缩放走。在 150% 缩放的屏幕上,你设的 6 像素间距看起来只有 4 像素,界面会显得特别挤。稳妥的写法是从Display拿 DPI,算一个缩放系数:
Display display = Display.getCurrent(); int dpi = display.getDPI().x; // 通常 96 double scale = dpi / 96.0; // 150% 屏约等于 1.5 layout.spacing = (int) Math.round(6 * scale); layout.marginLeft = (int) Math.round(12 * scale);第六,也是我最想说的一条:RowLayout的参数最好不要散落在各处。我见过一个类里spacing被设置了三次,分别在构造时、事件回调里、和某个"初始化样式"的方法里,最后一次覆盖前一次,谁也不知道最终值是多少。把布局参数的设置收敛到一个buildLayout()方法里,其它地方只改RowData的字段,出了问题只看一个地方就够。
我个人在实际维护老界面时的体会是,RowLayout的问题从来不是"它能力不够",而是"它的默认值太保守、太不显眼"。默认 3 像素的边距让人觉得它"就那样",fill和justify两个开关的名字又太像,exclude藏在RowData里没几个人会去翻文档。把这些细节都摊开调一遍之后,它在工具栏、标签流、条件筛选条这些场景里的性价比,其实比手写GridLayout高不少——尤其是需要"窄了就折行"这种活的时候,GridLayout得写一堆列数计算和空位填充,RowLayout一行wrap = true就完事了。