「难看」的七种可名状的病
「难看」是一个没法修的词。它不指向任何具体的东西,所以你只能瞎调。这一章把它拆成七个有名字的毛病——每一个都有一条能在代码里查出来的判据。
为什么要给它们起名字
第 1 章讲了「每个数要有出处」,第 2 章讲了「每个元素要答一问」,第 3 章讲了「层级可以量」。这三章解决的都是怎么做对。
但你实际遇到的场景通常是反过来的:东西已经做完了,看着不对,要找出哪儿不对。
这时候「难看」这个词是有害的,因为它把七种完全不同的问题压成了一个模糊的感受。就像把「肚子疼」当成一种病——它可能是胃、可能是阑尾、可能是你昨晚吃多了,治法完全不同。
七个词,每个词配一条能查的判据。判据的标准很硬:你打开代码或者截图,能在一分钟内回答是或否。
不满足这个标准的说法(比如「视觉不够高级」「缺少呼吸感」)一律不收——它们和「难看」一样没用。
七种病
逐个说一遍,顺便交代它们各自归哪一卷管。
挤 —— 什么都贴在一起
判据:相邻元素间距小于 8,并且组内间距和组外间距一样宽。
第二句才是关键。单纯「间距小」不一定是病——信息密集的表格就该密。真正的病是组内组外没有区别:所有东西均匀地贴在一起,于是分组信息完全丢失,用户看到的是一堵墙。
归卷 II(第 5、6 章)。
散 —— 什么都离得一样远
判据:所有间距里,最大的那次「跳变比」小于 1.5 倍。
「挤」的孪生兄弟。间距很大,但同样是均匀的大。结果一样:没有分组。第 5 章那台聚类引擎会把这个数字直接算给你看。
归卷 II(第 5 章)。
花 —— 字号、字重、颜色全在变
判据:一屏上超过 4 个字号,或超过 3 个字重。
这就是第 1 章那 18 个字号的直接后果。「花」的感受来源是:每一个层级都动用了全部手段,于是所有层级都在喊,谁也压不住谁。
归卷 III(第 9、12 章)。
平 —— 看不出哪个重要
判据:眯眼之后,权重最高的两个相差不到 20%。
「花」的反面,但同样致命。所有东西都是中等重量,于是眼睛没有落点,只能从左上角开始逐行扫描——那是在读文档,不是在用界面。
归第 3、12 章。
脏 —— 灰得发糊、颜色发脏
判据:正文对比度低于 4.5:1;或者一组灰色的色相在乱跳(有的偏蓝有的偏黄)。
「脏」有两个来源,而且都很好查。一是对比度不够——这是能算出确切数字的(第 14 章)。二是中性色不成体系:你的 #9E9E9E、#8A8A8A、#757575 各来自不同的地方,色相互相打架。
归卷 IV(第 13、14 章)。
歪 —— 看着没对齐但说不出哪儿
判据:元素边缘不落在 8 的倍数上;或者图标是按几何中心居中的。
第二条是那个让人抓狂的:明明代码里写的是 Alignment.Center,看上去就是偏了一点。答案是几何中心不等于视觉中心——第 7 章会把播放三角形的质心精确算出来给你看(往右挪 16.7%,24px 的图标就是 4px)。
归卷 II(第 6、7 章)。
硬 —— 状态切换很生硬、点了没反应
判据:可点元素没有 pressed 态;或者转场时长是 0,或者超过 500ms。
「硬」是唯一一个静态截图看不出来的病,所以它最容易漏。而它对「像样」的杀伤力很大:一个点下去毫无反馈的按钮,会让人以为 App 卡了。
归卷 V(第 18、20 章)。
这七个词怎么用
用法很简单:觉得不对的时候,按顺序问一遍。
| 顺序 | 问 | 为什么排这个位置 |
|---|---|---|
| 1 | 挤 / 散? | 间距问题最常见,也最好改——改完往往其他问题跟着消失 |
| 2 | 平? | 层级是骨架。骨架不对,改颜色改字号都是白费 |
| 3 | 花? | 和「平」是一对,一起判断:是没层级,还是层级太多 |
| 4 | 脏? | 能算出确切数字,最容易变成自动化检查 |
| 5 | 歪? | 通常是最后收尾的活 |
| 6 | 硬? | 静态图看不出来,必须真机点一遍 |
因为前面的病会伪装成后面的病。
间距乱的界面看起来会「脏」,于是你去调颜色——调不好,因为病根不在颜色。层级不对的界面看起来会「花」,于是你去减字号——减完变成「平」,因为你减错了地方。
按这个顺序查,能避免绝大部分的白费功夫。
一个反例:什么不是病
为了让这七个词有边界,说几个不算病的东西:
- 信息密度高——密不是病。财务软件、代码编辑器、航班信息屏都该密。密而不乱靠的是分组和对齐,不是留白。
- 用了系统默认样式——默认样式通常是对的。「看起来像原生 App」不是缺点,那是几百人打磨过的结果。
- 没有插画、没有渐变、没有动效——这些是可选项,不是及格线。一个只有文字和按钮但间距对齐颜色全对的界面,比一个满是渐变但间距乱的界面像样得多。
- 和竞品长得像——同一类产品的用户有同一套预期。刻意做得不一样,代价是用户要重新学。
把「不够特别」当成病来治。
你的界面看起来平平无奇,这通常说明它是对的。用户打开一个 App 不是来欣赏的,是来办事的——办完事之后他不该记得你的界面长什么样。
「像样」的天花板不是「惊艳」,是「顺手到没人提起」。这是这本书唯一一处需要你调整期待的地方。
七条里有五条可以变成自动化检查。现在先看形式,第 24 章给完整版:
// 脏:对比度。可以写成真正的单元测试
@Test
fun bodyTextMeetsAA() {
val ratio = contrastRatio(
MaterialTheme.colorScheme.onSurface,
MaterialTheme.colorScheme.surface
)
assertThat(ratio).isAtLeast(4.5)
}
// 硬:触摸目标。Compose 测试里能直接量
composeTestRule.onNodeWithTag("like")
.assertHeightIsAtLeast(48.dp)
.assertWidthIsAtLeast(48.dp)
// 花:字号字面量。一条 lint 规则就够
// 禁止 UI 层出现裸的 .sp / .dp,只允许 MaterialTheme.* 和 Spacing.*
剩下两条(「平」和「状态不只靠颜色」)目前只能靠眼睛——但也只剩两条了。
卷 I 结束
到这里,你手上有了四样东西:
接下来六卷,一卷关一类自由度。下一卷是距离——这本书里性价比最高的一卷,因为间距是所有维度里最便宜改、见效最快的一个。
- 挤——组内间距 : 组外间距至少 1 : 2,不再凭感觉留白。
- 散——间距必须有明显跳变(≥ 1.5 倍),均匀等于没分组。
- 花——一屏字号 ≤ 4 个、字重 ≤ 3 个。
- 平——眯眼后前二权重必须拉开 20% 以上。
- 脏——正文对比度 ≥ 4.5:1,灰色全部从同一条中性阶取。
- 歪——边缘落在 8 的倍数上,图标按视觉质心补偿。
- 硬——每个可点元素有 pressed 态,转场时长取 token 且 ≤ 500ms。