少用线
这是一条改起来最快、见效最明显的规则。你界面上大半的杂音来自边框,而其中大半根本不需要存在。
边框在做什么
你画一条边框,是想说「这里是一个区域的边界」。但你实际做的事有两件:
- 标出了边界(你想要的);
- 在画面上加了一条真实的线(你没想要的)。
第二件事的代价比想象中大。那条线会参与视觉层级(第 3 章:它有面积、有亮度差)、会需要和别的线对齐(第 7 章)、会在暗色模式下需要重新调、还会和它旁边的另一条边框叠成两像素粗。
顺序不是随便排的,是按「引入了多少新东西」排的。间距不引入任何可见物;底色只改一个已有的属性;阴影引入一个新的高度层级;边框引入一条新的线。
四种切一遍。你会发现前三种看起来都比第四种干净——而信息一点没少。
间距为什么排第一
因为它顺带干了第二件事:表达分组。
第 5 章那条「组内 : 组外 ≥ 1 : 2」已经说过——间距在声明「谁跟谁是一伙的」。所以当你用间距分隔时,你不只是把两块东西分开了,你还说清楚了它们的关系。
边框做不到这个。三条一样的边框,只能说「有三个东西」,说不了「前两个是一组」。
卡片既有阴影又有边框。
这两个在说同一件事(这是一块独立的面),于是其中一个是纯粹的噪音。M3 的 Card 和 OutlinedCard 是两个不同的组件,就是这个意思——选一个。
类似的还有:列表项之间既有分隔线又有间距;输入框既有边框又有填充底色;按钮既有填充又有描边。
底色差多少才合适
「换底色」这一档需要一个具体的数,否则你会调过头。
| 两块面 | 对比度 | 感知差 ΔE | 判定 |
|---|---|---|---|
| #FFFFFF / #F9FAFD | 1.044 | 0.0154 | 刚好 |
| #FFFFFF / #F4F6F8 | 1.083 | 0.0280 | 刚好 |
| #FFFFFF / #EFF0F3 | 1.140 | 0.0450 | 刚好 |
| #FFFFFF / #DEE0E2 | 1.324 | 0.0943 | 偏重 |
低于 1.03——看不出来,等于没分。
高于 1.3——那已经不是「分隔」了,是「这是两个不同的区域」。用户会读成两个板块,而不是一个板块里的两项。
实践上:用中性阶相邻的两级就对了。这也是第 13 章那条中性阶为什么要排得密的原因之一。
什么时候边框确实是对的
不是要你删光所有边框。有三种情况边框是最优解:
- 输入框——边框在回答第 2 章的「我能做什么」。一个没有边界的输入框,用户不知道能点。(而且 WCAG 要求它对背景 ≥ 3:1。)
- 次要按钮——
OutlinedButton的边框是它「可点但不是主角」的身份标识。 - 需要在任何背景上都成立的容器——比如可能被放在图片上的卡片。阴影和底色都会失效,边框不会。
它们的边框都不是在「分隔」,而是在标识一个可交互的元素。
所以这一章的规则可以说得更准一点:用边框来说「这里能操作」,不要用边框来说「这里分开了」。
线的另一种用法:分隔线
长列表里的分隔线(Divider)值得单独说,因为它是唯一一个「大量重复出现的线」。
每一项之间都画满宽的线。
- 屏幕上出现十几条平行线
- 线本身成了最抢眼的东西(第 3 章:面积大)
- 和左边距对不齐时特别脏
能不画就不画;非画不可时缩进对齐。
- 行高够大时,间距已经足够分隔
- 要画就缩进到内容左边缘,别顶满
- 只在分组之间画,不在每一项之间画
// ✗ 每项一条满宽线
LazyColumn {
items(settings) { item ->
SettingRow(item)
HorizontalDivider() // 十几条平行线
}
}
// ✓ 只在分组之间,而且缩进对齐内容
LazyColumn {
settingGroups.forEachIndexed { i, group ->
if (i > 0) {
HorizontalDivider(
modifier = Modifier.padding(start = MaterialTheme.spacing.base),
color = MaterialTheme.colorScheme.outlineVariant // 比 outline 更淡
)
}
items(group.items) { SettingRow(it) }
}
}
// ✓✓ 更好:连线都不要,用底色分组
// 每组是一块 surfaceContainerLow 的圆角面,组之间留 24dp
// —— 这就是 iOS 设置页和 M3 列表现在的做法
注意 outlineVariant 这个角色:M3 专门给了一个「比 outline 更淡的线色」,用途就是分隔线。分隔线不该用和输入框边框一样的颜色——后者要过 3:1,前者不该那么抢眼。
- 「这两块怎么分开」——按间距 → 底色 → 阴影 → 边框的顺序试,用第一个成立的。
- 「底色差多少」——对比度 1.04 到 1.15,也就是中性阶相邻两级。超过 1.3 就成了两个区域。
- 「列表要不要画分隔线」——默认不画。要画就只在分组之间、缩进对齐内容、用
outlineVariant。边框只用来说「这里能操作」。