卷 VII · 表面· CH 26· 分隔手段对照 深度 26/28

用线

这是一条改起来最快、见效最明显的规则。你界面上大半的杂音来自边框,而其中大半根本不需要存在。

四种分隔手段底色差 1.04–1.14边框排最后

边框在做什么

你画一条边框,是想说「这里是一个区域的边界」。但你实际做的事有两件:

  1. 标出了边界(你想要的);
  2. 在画面上加了一条真实的线(你没想要的)。

第二件事的代价比想象中大。那条线会参与视觉层级(第 3 章:它有面积、有亮度差)、会需要和别的线对齐(第 7 章)、会在暗色模式下需要重新调、还会和它旁边的另一条边框叠成两像素粗。

《Refactoring UI》的规矩:先试前三种,最后才画线
① 加间距视觉成本 ★
② 换底色★★
③ 加阴影★★★
④ 画边框★★★★

顺序不是随便排的,是按「引入了多少新东西」排的。间距不引入任何可见物;底色只改一个已有的属性;阴影引入一个新的高度层级;边框引入一条新的线。

四种切一遍。你会发现前三种看起来都比第四种干净——而信息一点没少。

间距为什么排第一

因为它顺带干了第二件事:表达分组

第 5 章那条「组内 : 组外 ≥ 1 : 2」已经说过——间距在声明「谁跟谁是一伙的」。所以当你用间距分隔时,你不只是把两块东西分开了,你还说清楚了它们的关系。

边框做不到这个。三条一样的边框,只能说「有三个东西」,说不了「前两个是一组」。

最常见的一处冗余

卡片既有阴影又有边框

这两个在说同一件事(这是一块独立的面),于是其中一个是纯粹的噪音。M3 的 CardOutlinedCard 是两个不同的组件,就是这个意思——选一个

类似的还有:列表项之间既有分隔线又有间距;输入框既有边框又有填充底色;按钮既有填充又有描边。

底色差多少才合适

「换底色」这一档需要一个具体的数,否则你会调过头。

两块面对比度感知差 ΔE判定
#FFFFFF / #F9FAFD1.0440.0154刚好
#FFFFFF / #F4F6F81.0830.0280刚好
#FFFFFF / #EFF0F31.1400.0450刚好
#FFFFFF / #DEE0E21.3240.0943偏重
用底色分隔,对比度落在 1.04 到 1.15 之间

低于 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,前者不该那么抢眼。

本章关掉的自由度本章 3 条 · 累计 73 / 78
  • 「这两块怎么分开」——按间距 → 底色 → 阴影 → 边框的顺序试,用第一个成立的。
  • 「底色差多少」——对比度 1.04 到 1.15,也就是中性阶相邻两级。超过 1.3 就成了两个区域。
  • 「列表要不要画分隔线」——默认不画。要画就只在分组之间、缩进对齐内容、用 outlineVariant。边框只用来说「这里能操作」。