控件 Widget
打开任何一个软件,你看到的东西看起来千差万别,其实拆开来只有二三十种零件在反复组合。按钮、输入框、下拉菜单、勾选框、滑块、列表、标签页——它们就是界面的乐高积木。学会认这些积木,再复杂的界面你都能一眼看穿它的骨架。
你去宜家逛一圈,几百件家具看得眼花。但翻开安装说明,会发现所有家具都是那几样东西拼出来的:板材、螺丝、插销、合页、封边条。
书架和衣柜的区别,不在于用了不同的零件,而在于零件的尺寸、数量和拼装方式不同。
界面也是这样:微信和银行 App 看起来毫无关系,但把它们拆到最底层,都是按钮、文本、图片、列表这几样东西按不同方式摆出来的。
先把这些词翻成人话
这一节的英文词特别多,而且经常混着用。先一次性对齐,后面就顺了。
- Widget / Control / Component三个词指的是同一件东西:界面上一个预制好的零件。Widget(小部件)是 Unix 和安卓那边的叫法,Control(控件)是 Windows 那边的叫法,Component(组件)是网页前端的叫法。说白了就是同一样东西在三个行业有三个方言名,跟"土豆、马铃薯、洋芋"一个道理。
- 控件大白话定义:装修用的成品门窗。你不会自己刨木头做一扇门,而是量好尺寸去买一扇成品——外框、合页、锁孔、密封条全都装好了,你只管往墙洞里塞。控件就是界面上的成品门窗。
- 容器容器(Container,一种专门用来"装别的控件"的控件)。它干的活儿相当于托盘和餐桌:本身不能吃,但决定了盘子怎么摆。
- 状态状态(State,控件此刻的情况——勾了没勾、写了什么字、能不能点)。听着玄,其实就是控件的记忆。
- 回调回调(Callback,你事先交给控件保管的一段代码,等事情发生时由它替你调用)。好比你在餐厅留了手机号说"位子空了打给我"——号码交出去了,什么时候打由店家决定。
- 设计系统设计系统(Design System,一整套规定了颜色、间距、字号、控件长相的规范和现成代码)。简单说就是一家连锁店的装修手册:桌椅型号、灯光色温、招牌字体全都写死,所以你在哪家分店都觉得是同一家。
- 可访问性可访问性(Accessibility,常缩写为 a11y——让看不见、听不见、手不方便的人也能用上这个软件)。它相当于建筑里的无障碍坡道和盲道:不是给大多数人用的,但少了它,一部分人就完全进不来。
- 焦点焦点(Focus,键盘当前"说话"的对象是谁)。本质上就是会议里的话筒在谁手上:你按键盘,字进哪个输入框,取决于话筒给了谁。
控件到底是什么:可复用的界面积木
先给一个不绕弯子的定义:控件是一个把"长什么样"和"怎么响应用户"打包在一起的可复用单元。
注意"打包"这个词。一个按钮不只是屏幕上那个圆角矩形——如果只是画个矩形,那叫图形,不叫控件。按钮之所以是控件,是因为它同时还知道:鼠标移上来要变亮、按下去要变暗、被点击时要通知程序、被禁用时要变灰而且不再理你。外观和行为绑在一起,才叫控件。
再注意"可复用"。这是控件存在的全部理由。想象一下如果没有控件:你要在界面上放 30 个按钮,就得手写 30 遍"画矩形、画文字、检测鼠标是否在矩形内、检测是否按下、改颜色、触发回调"。有了控件,你只需要说 30 次"给我一个按钮,文字是 X,点了以后做 Y"。剩下的脏活,控件库替你干过一遍,全世界共享。
这就是软件工程里最朴素也最有效的思想——把重复的东西抽象成零件,让人只关心零件之间的差异。控件是这个思想在界面领域的产物。
乐高的伟大之处不在于某一块积木特别精巧,而在于所有积木的接口是统一的——凸起和凹槽的尺寸标准化了,于是任意两块都能拼。
控件库就是一套标准化的界面积木:每个控件都有统一的"接口"(怎么设置大小、怎么放进容器、怎么绑定事件)。
所以你学会用一个按钮,基本就学会了用所有控件——因为它们的"凸起和凹槽"是一个模子刻的。
常见控件的五大类
控件的种类看起来很多,但按"它在界面里干什么活"来分,只有五类。记住这五类,以后见到任何陌生控件,都能先给它归个位。
| 分类 | 它的职责 | 典型成员 |
|---|---|---|
| 输入类 | 把用户的意图收进来 | 单行输入框、多行文本域、密码框、勾选框 Checkbox、单选组 Radio、开关 Switch、滑块 Slider、下拉选择 Select、日期选择器、数字步进器、文件选择器 |
| 展示类 | 把信息摆出来给人看 | 文本标签 Label、图片 Image、图标 Icon、分割线 Divider、徽标 Badge、头像 Avatar、表格 Table、树 Tree、代码块、二维码 |
| 容器类 | 装别的控件,负责排布 | 面板 Panel、卡片 Card、分组框 GroupBox、滚动区域 ScrollView、栅格 Grid、行/列布局 Row/Column、可折叠面板 Collapse、分栏 Splitter |
| 导航类 | 决定"你现在在哪、能去哪" | 菜单栏 Menu、右键菜单、标签页 Tabs、侧边栏 Sidebar、面包屑 Breadcrumb、分页器 Pagination、步骤条 Steps、工具栏 Toolbar |
| 反馈类 | 告诉用户"发生了什么" | 对话框 Dialog、轻提示 Toast、进度条 ProgressBar、加载转圈 Spinner、气泡提示 Tooltip、通知 Notification、骨架屏 Skeleton |
这五类之间的关系很像一家餐厅的分工:输入类是点单的服务员,负责把你的要求记下来;展示类是端上桌的菜品和菜单,让你看到东西;容器类是桌子和托盘,决定东西怎么摆;导航类是门口的指引牌和楼层图,告诉你哪儿是包间哪儿是散台;反馈类是那句"您的菜已经在做了,请稍等十分钟"——不干实事,但少了它你会焦虑。
特别值得说的是反馈类。新手做界面最容易漏掉的就是这一类。功能都做对了,点击后要等三秒才有结果,中间什么提示都没有——用户会以为程序卡死了,然后疯狂重复点击。反馈类控件不产生任何"功能",但它决定了软件用起来是否让人安心。
输入类控件详解:什么时候用哪个,用错了会怎样
输入类是最容易用错的一类。表面上"都能收集用户的选择",但选错一个,用户就得多点三下、或者干脆看不懂。下面按选项个数和是否互斥两把尺子给它们排个座。
| 控件 | 该用它的场合 | 常见误用 | 生活对应 |
|---|---|---|---|
| 单选组(Radio) | 2 到 5 个选项,只能选一个,而且你希望用户同时看见所有选项 | 选项有二十个还硬用单选,把页面撑得老长 | 点菜时"米饭 / 面条 / 馒头"三个格子摊开让你划 |
| 下拉选择(Select) | 选项超过 6 个、或者选项本身不重要不值得占地方 | 只有两个选项还用下拉,害用户多点一次才看得见 | 点菜时那本要翻开才看得到内容的菜单 |
| 勾选框(Checkbox) | 可以选多个、也可以一个都不选 | 用来表示"是否开启某功能"却不立即生效,用户以为已经生效了 | 快递单上的"需要保价 / 需要签收回单",各自独立勾 |
| 开关(Switch) | 只有开和关,而且一拨就立刻生效 | 放在一个还需要点"保存"才生效的表单里,语义就矛盾了 | 墙上的灯开关:一按就亮,没有"确定"这一步 |
| 滑块(Slider) | 连续量、而且用户在意的是"大概位置"而不是精确数字 | 需要精确到个位数时用滑块,用户拖不准,抓狂 | 洗衣机的水温旋钥:你要的是"温一点",不是"38.5 度" |
| 数字步进器(Stepper) | 整数、小范围、需要精确 | 范围到几千还让人一下一下点 | 点菜时那个"份数 - 2 +" |
| 单行输入框 | 内容长度不确定但一定很短:姓名、手机号 | 拿它收地址,用户看不见自己写全没写全 | 快递单上那一小格"寄件人姓名" |
| 多行文本域 | 内容可能好几行:备注、留言、地址 | 反过来,只填一个数字也给个大方框,用户以为要写作文 | 快递单最下面那一大块"备注" |
| 日期选择器 | 凡是日期,都别让人手打 | 让人手输"2024/3/5"还是"2024-03-05",格式错到崩 | 医院挂号时那个可以翻月份的日历 |
这里面勾选框和开关的区别最值得单独讲,因为几乎所有新手都混过。判断标准只有一句话:开关一拨就生效,勾选框要等你点"保存"或"确定"才生效。
打个比方。开关是墙上的电灯开关:手一按,灯就亮,没有第二步。勾选框是快递单上打的勾:你在"需要保价"前面画了个勾,这时候什么都还没发生,得等你把单子递给快递员(点"提交"),事情才真正办成。
所以在一个带"保存"按钮的设置页里放开关,就会造成一种很微妙的困惑:用户拨完开关就以为改完了,直接关掉页面,结果设置没生效。这不是用户笨,是控件说错了话。控件的形状本身就在向用户承诺某种行为,承诺和实际不符,就是设计缺陷。
展示、导航、反馈三类的选择要点
输入类讲完,剩下三类各挑最容易搞错的说清楚。
- 表格 还是 列表表格(Table)适合"每条数据有很多个字段,而且你要横向对比",比如订单号、金额、状态、时间四列并排。列表(List)适合"每条数据主要就一两个信息,重点是纵向浏览"。判断法很土:你会不会想把某一列拿出来从大到小排一遍?会,就用表格;不会,就用列表。好比菜市场的价格牌是表格(品种、单价、产地要对比),而饭馆的菜单是列表(一道道往下看)。
- 树 什么时候用树(Tree)只在数据本身真的有"父子层级"时才用,比如文件夹、组织架构、评论回复。它对应的现实物件是公司的组织架构图,或者图书馆的分类号体系。如果你的数据是平的,硬做成树只会多点几下才看得到东西。
- 标签页 还是 侧边栏标签页(Tabs)适合 2 到 6 个平级的分类,而且用户会来回切。侧边栏(Sidebar)适合分类多、还带二级层级。数量是分界线:超过六个标签页会挤成一排小字,那时候就该换侧边栏了。类比很直接:标签页是文件夹上贴的几个彩色标签条,侧边栏是整个文件柜的抽屉列表。
- 面包屑 解决什么面包屑(Breadcrumb,页面顶部那串"首页 › 分类 › 详情")解决的是"我现在在哪、怎么退回上一层"。它就是大商场里那块"您当前在 3 楼 · 女装区 · B 通道"的指示牌。层级深的地方必须有,否则用户会迷路然后放弃。
- 对话框 还是 轻提示这是反馈类最常见的错。对话框(Dialog)会挡住整个界面、强迫你做选择,所以只能用在"这件事不确认就没法继续"的时候,比如"确定要永久删除吗"。轻提示(Toast)是屏幕角上飘一下就消失的小条,用在"告诉你一声,但不用你回应",比如"保存成功"。判断标准:需要用户做决定,用对话框;只是通报一声,用轻提示。
- 进度条 还是 转圈进度条(ProgressBar)用在"知道总量、能算出百分比"的时候,比如下载一个 200 MB 的文件。转圈(Spinner)用在"完全不知道要多久"的时候。两者混用会造成很糟的体验:假的进度条卡在 99% 不动,比什么都不显示更让人恼火。这就像公交站的到站预报:能报"3 分钟后到"就报,报不了就别乱报一个"1 分钟"然后让人等十分钟。
- 骨架屏 的妙用骨架屏(Skeleton,加载时先画出内容的灰色轮廓)。它的作用不是加快速度,而是让等待显得短。因为人看到"东西的形状已经出来了",会本能地觉得快到了。好比餐厅先给你上餐具、倒水、摆好碟子——菜一口没上,但你的焦虑明显降了。
关于反馈类再补一个能直接用的数字标准。人对等待的感受不是线性的,业界有一组经验值:
| 耗时 | 用户感受 | 你该给什么反馈 |
|---|---|---|
| 0.1 秒以内 | 感觉是"瞬间",跟自己手的动作连在一起 | 什么都不用给 |
| 0.1 到 1 秒 | 能感觉到有延迟,但注意力还在 | 按钮进入按下状态就够了 |
| 1 到 10 秒 | 开始怀疑"是不是没点上",会重复点击 | 必须给转圈或进度条,并且把按钮禁用掉 |
| 10 秒以上 | 注意力已经离开,会去干别的事 | 给能看到进展的进度条,最好加"预计剩余时间",允许取消 |
第三行那个"把按钮禁用掉"是新手最常漏的一步,后果却很严重。用户点了没反应,就再点,再点——如果后端每次都真的执行,那就是重复下单三次。这就像窗口办事时递了申请单没听到回应,你就又抄一张递进去,最后系统里出现三份重复申请。解决办法很简单:点下去的那一瞬间就把按钮变灰,办完再恢复。
控件三要素:外观 + 状态 + 行为
任何一个控件,无论多简单多复杂,都可以拆成三个部分来理解。这是本节最值得记住的一个模型。
:active 两个伪类,顺序写错会互相覆盖。">- 外观 Appearance它长什么样:尺寸、颜色、圆角、字体、边框、阴影、图标。外观是"给眼睛看的部分",通常由样式表或主题统一控制,改起来最便宜。
- 状态 State它现在处于什么情况:勾选框是选中还是未选中、输入框里现在是什么文字、按钮是可用还是禁用、列表选中了第几行。状态是"控件的记忆",是数据。
- 行为 Behavior它对用户动作怎么反应:点击了要做什么、输入了要校验什么、拖动了要更新什么。行为是"控件的脾气",通常通过回调函数交给你来定义。
这三者的关系是有方向的:用户操作触发行为 → 行为改变状态 → 状态决定外观。
拿一个勾选框举例。你点它一下(用户操作),触发了"切换选中"这个行为,行为把状态从"未选中"改成"选中",然后外观自动从空方框变成打钩的方框。整条链是单向流动的。现代界面框架(React、Vue、Flutter、SwiftUI 等)之所以都强调"状态驱动 UI",本质就是把这条链明确成规矩:你只管改状态,界面长什么样由框架自动推导,不许手动去改外观。
为什么这条规矩重要?因为一旦允许手动改外观,就会出现"状态说没选中,但界面上打着钩"这种自相矛盾的 bug。这类 bug 极难查,因为真相有两份,而且它们不一致。
// 控件三要素在代码里的样子(伪代码)
Button {
// ① 外观
text: "提交订单",
width: 120,
color: "#556b3d",
radius: 6,
// ② 状态
enabled: false, // 现在是禁用的
loading: false, // 没有在转圈
// ③ 行为
onClick: function () {
this.loading = true; // 改状态
submitOrder(); // 干实事
}
}
控件树:界面是一棵嵌套的树
控件从来不是散落一地的,它们是嵌套的——容器类控件里可以放别的控件,而放进去的那个如果也是容器,里面又能再放。层层套下去,整个界面就成了一棵树。
这棵树有一个根(通常是窗口),中间是各种容器(面板、卡片、布局),叶子是那些实心的控件(按钮、文字、图片)。你在屏幕上看到的每一个像素,都归属于这棵树上的某个节点。
Window(窗口,根节点)
└── Column(竖向排布容器)
├── Toolbar(工具栏)
│ ├── Button "新建"
│ ├── Button "打开"
│ └── Button "保存"
├── Row(横向排布容器)
│ ├── Sidebar(侧边栏,宽 200)
│ │ └── Tree(文件树)
│ └── ScrollView(可滚动区域,占剩余空间)
│ └── TextArea(正文编辑区)
└── StatusBar(状态栏)
├── Label "第 12 行,第 4 列"
└── Label "UTF-8"
树形结构不是为了好看,它带来三个实实在在的好处。
第一,位置可以继承推算。子控件的坐标通常是相对父控件的。父控件整体移动 100 像素,所有子孙自动跟着移动,你不需要逐个去改。这就像搬桌子——桌上的杯子盘子不用一个个搬。
第二,属性可以向下传递。父容器设置了字体、颜色主题、是否禁用,子控件默认继承。所以切换深色模式时,你只要改根节点的主题,整棵树跟着变。
第三,事件可以顺着树跑。你点在按钮上,这个点击事件先从根节点往下"捕获",找到最深的那个被点中的控件,再从它往上"冒泡"。这套机制让"点击子元素时父元素也能知道"变得非常自然——比如点卡片里的任意位置都算点了整张卡片。这一点在下一节讲事件驱动时会展开。
控件树最像行政区划:国 → 省 → 市 → 区 → 街道 → 门牌号。
你的地址(坐标)是相对上一级说的,"3 号楼"只有在某个小区里才有意义。
政策(样式主题)从上往下发,省里定的规矩市里默认执行,除非市里特别声明例外——这就是"样式继承"和"局部覆盖"。
而一件事情上报(事件冒泡),是从门牌号一级级往上传的。
原生控件 vs 自绘控件:一道永恒的取舍
做界面时有个绕不开的选择:按钮到底用操作系统提供的那个,还是自己画一个?
原生控件(Native)是操作系统自带的。你要一个按钮,系统就给你一个 Windows 味或者 macOS 味的按钮,长相、动画、字体、无障碍支持全是系统那一套。
自绘控件(Custom-drawn)是框架自己从零画的——只借用系统的"一块空白画布",然后所有像素都自己填。Flutter、Qt 的 QML、游戏引擎里的 UI、浏览器里的绝大多数组件库,走的都是这条路。
| 对比维度 | 原生控件 | 自绘控件 |
|---|---|---|
| 视觉一致性 | 和系统其他软件浑然一体 | 和自己家产品在各平台一致 |
| 跨平台表现 | 各平台长得不一样,得分别适配 | 一套代码,各平台像素级相同 |
| 定制自由度 | 低,改个圆角都可能很别扭 | 极高,想画成什么样就什么样 |
| 无障碍 / 输入法 | 免费获得屏幕阅读器、中文输入法、放大镜支持 | 要自己实现,很容易做不全 |
| 性能与体积 | 轻,系统已经加载好了 | 需要带渲染引擎,包体更大 |
| 随系统进化 | 系统换新设计语言,你自动跟上 | 系统更新了,你还是老样子 |
怎么选?给一条实用的判断标准:工具类软件优先原生,品牌类软件优先自绘。
系统设置、文件管理器、开发工具这类东西,用户希望它"像本地软件",快、稳、不出戏,原生控件最合适。而设计工具、音乐播放器、游戏、需要强品牌调性的 App,用户期待的是"独特的视觉体验",自绘更能表达设计。
顺便说一句,这两条路正在互相靠近。原生控件库越来越可定制,自绘框架也在拼命补齐无障碍和输入法这块短板。但这道取舍本身不会消失——它是"融入环境"和"保持自我"之间的永恒张力。
控件库与设计系统:连锁店的装修手册
你可能注意到一个现象:同一家公司的十个产品,长得像一家人;而两家不同公司的产品,就算功能一样,气质也完全不同。这背后不是设计师默契,是设计系统在起作用。
设计系统(Design System,一整套规定了颜色、间距、字号、圆角、动画时长、以及每个控件长什么样的规范文档 + 现成代码库)。它干的活儿相当于连锁餐厅的开店手册:桌椅型号、灯光色温、招牌字体、餐具尺寸、菜品摆盘全写死。于是不管在哪个城市,你一进门就知道"这是那家店"。
| 设计系统 | 出自 | 视觉气质 | 它最看重什么 |
|---|---|---|---|
| Material Design | 纸片层叠、明显阴影、大色块、按下有水波纹 | 用"纸和墨"的物理隐喻解释层级,动效必须有因果 | |
| Fluent Design | Microsoft | 半透明毛玻璃、柔和光影、圆角 | 兼顾键鼠、触摸、笔、语音多种输入方式 |
| Human Interface Guidelines | Apple | 克制、留白多、字重变化承担层级 | 控件行为高度统一,尽量让人"不用学" |
| Ant Design | 蚂蚁集团 | 信息密度高、表格和表单极强 | 专为"一屏放很多数据"的企业后台优化 |
为什么 Ant Design 那一行说"信息密度高"?因为它面对的场景不同。面向消费者的 App 追求好看好上手,面向内部员工的后台系统追求一屏能看多少条数据。打个比方:前者是商场的展厅,货品摆得疏、灯光打得好、留白多;后者是仓库的货架,一寸空间都要用上,工作人员天天在这儿,效率比美观重要。设计系统之间的差异,本质上是它们服务的人群和使用频率的差异,不是审美高低。
用设计系统的三个实际好处,说明为什么团队几乎必须用:
- 省掉了无数次争论按钮圆角是 4 还是 6、间距是 12 还是 16、警告色用哪个红——这些讨论一次要开半小时会,而且没有客观答案。设计系统把它们一次性定死,团队从此不再讨论。好比装修队按图施工,不用每砌一堵墙都开会讨论砖缝宽度。
- 保证边角情况被想过你自己写的按钮,可能只测了"正常"这一种情况。成熟设计系统的按钮,已经被几百万个页面用过,禁用态、加载态、超长文字换行、深色模式、键盘焦点框全都推敲过。这是它最大的隐藏价值。
- 新人上手快团队来了新同事,不需要摸索"我们家的风格是什么",看手册照着搭就行。而用户也不需要重新学:新功能上线,它长得跟老功能一样,用户直接会用。
控件的可访问性:那条必须留的坡道
可访问性(Accessibility,也写作 a11y——让视力、听力、行动能力不同的人也能正常使用软件)。这一节最容易被跳过,但它是区分"做完了"和"做好了"的分水岭。
先建立一个基本认知:需要它的人比你想的多得多。换算成能感知的数字——世界上有大量人存在色觉障碍(男性中比例约 8%,也就是每 12 个男性里有 1 个),视力严重受损的人以亿计,此外还有临时性的:手骨折了只能用一只手、坐在阳光下屏幕反光看不清对比度低的文字、抱着孩子只能用一只手操作手机。无障碍设计从来不只服务"残障人士",它服务的是"任何一个此刻不方便的人",而这个集合包括所有人。
控件层面要做的事,其实就四条,都不难:
- 键盘必须能走到所有能点的东西,都必须能用
Tab键走到、用Enter或空格触发。而且走的顺序要合理(从上到下、从左到右)。好比一栋楼不光要有电梯,楼梯也得能通到每一层,而且不能通到一半是死路。最常见的违规是用div硬做一个"假按钮"——鼠标能点,键盘完全走不到。 - 焦点框千万别删很多人觉得那圈蓝色轮廓不好看,就一行 CSS 把它去掉了。对纯键盘用户来说,这等于把整栋楼的灯全关了——他完全不知道自己现在站在哪。要改,可以改成好看的样式,但绝不能删。
- 要有"读得出来"的名字屏幕阅读器(把界面念给盲人听的软件)读到一个只有图标的按钮,如果没有文字说明,它只能念"按钮",用户完全不知道那是干什么的。所以纯图标按钮必须配一个隐藏的文字标签。这相当于给盲道尽头那个门加一块盲文牌。
- 不能只靠颜色传达信息"必填项标成红色"对色觉障碍者可能完全无效。正确做法是颜色 + 图标 + 文字三重表达。想象一下红绿灯:如果只靠颜色区分,色盲司机就没法开车了——所以红绿灯的位置也是固定的(红在上、绿在下),这就是"不只靠颜色"的经典解法。
最后给一个能立刻自查的土办法:把鼠标拔掉,只用键盘把你的界面从头走到尾,看能不能办完一件完整的事。这一招五分钟就能测,却能揪出九成的可访问性问题。
什么时候该自己造控件
上面一直在说"用现成的",但确实有该自己造的时候。给一个清单,凡是踩中其中一条,才考虑动手:
| 情况 | 该不该自造 | 为什么 |
|---|---|---|
| 现成控件只是颜色圆角不符合品牌 | 不该 | 改样式就行,不用重写行为。自造会白丢无障碍和输入法支持 |
| 现成控件的交互逻辑跟你的需求根本不同 | 该 | 比如你要一个"可以同时选一段范围、还能拖动两端"的时间轴,标准滑块做不到 |
| 需要展示上万条数据还要流畅滚动 | 该 | 标准列表会一次性画出所有行而卡死,得自己做"只画可见区域"的虚拟滚动 |
| 你的产品的核心竞争力就是这个交互 | 该 | 比如设计工具的画布、音乐软件的波形编辑器,这些就是产品本身 |
| 只是觉得"自己写更简单" | 不该 | 这是最常见的误判。简单的只是第一版,后面的状态组合、键盘支持、边界情况会淹死你 |
那句"自己写更简单"的错觉,值得用装修的例子破一下。你要装一扇门,看着好像"钉几块木板"就行——但成品门里其实包含了:合页的承重角度、门框的膨胀余量、锁舌的对位、密封条的防风、下沉后不刮地的余隙。你自己做的那扇门,第一天挂上去也能开合,一个月后就开始刮地、关不严、锁不上。控件一样:能显示能点击只是第一天的事。
移动端与桌面端的控件差异
同一个控件搬到手机上,往往必须换个形态。核心原因有三个,全是物理限制。
- 手指比鼠标粗得多鼠标指针的有效精度约 1 个像素,成年人指尖的接触面直径大约 7 到 9 毫米,在手机上折合四十几个像素。所以各平台都规定可点区域最小 44 到 48 像素。换算一下:约等于一颗黄豆的直径,比你想象的大。所以桌面上那种密密排的小图标,搬到手机上必须放大加间距,否则十次点错三次。
- 没有悬停,也没有右键上一节讲过,触摸屏没有"悬而未按"这个中间态。于是桌面上靠悬停显示的提示气泡在手机上完全失效,右键菜单也没了,只能改成长按或者左滑露出操作按钮。
- 屏幕小,而且手够不到全部单手拿手机时,拇指能舒服触到的只有屏幕下方约三分之二。所以移动端把主导航放在底部,而桌面放在顶部。这是纯粹的人体工程决定的,跟审美无关。
| 同一个需求 | 桌面端做法 | 移动端做法 |
|---|---|---|
| 主导航 | 顶部菜单栏 / 左侧边栏 | 底部标签栏(拇指能到) |
| 展示补充说明 | 鼠标悬停弹出气泡 | 旁边放一个问号图标,点开显示 |
| 对某一条做操作 | 右键弹出菜单 | 左滑露出按钮,或长按进入多选 |
| 从很多选项里选一个 | 下拉列表 | 从底部升起的半屏选择面板(选项更大更好点) |
| 填一张长表单 | 一屏放完,多列排布 | 拆成好几步,一屏一两个字段 |
| 确认危险操作 | 居中的模态对话框 | 底部升起的操作表,危险项标红放在最上 |
最后那一行的"危险项标红放最上"值得一提,它跟桌面习惯正好相反。桌面对话框通常把"取消"放在左边、"确定"放在右边;而移动端底部面板往往把最危险的那一项放在最上面、最下面留一个大大的"取消"。原因是拇指最容易误触的是屏幕最下方,所以那个位置要留给最安全的选项。这不是设计师换了品味,是同一条原则(把误触成本降到最低)在不同物理条件下推出的不同结论。
这就像地铁站的闸机和商场的旋转门:都是控制人流,但一个要求快速通过、一个要求防风保温,于是形态完全不同。你不会说其中一个"设计得不对"。
控件状态:一个按钮至少有五张脸
最后一个容易被新手忽略的点:控件不是只有一个样子。同一个按钮,在不同情况下要长得不一样,用户才能读懂"我现在能不能点、点了有没有反应"。
- 默认 Default / Normal什么都没发生时的样子。这是基准状态,其余状态都是在它基础上做加减。
- 悬浮 Hover鼠标移上去、但还没按下。作用是"预告"——告诉你这块东西是可以点的、而且你正瞄准着它。触摸屏没有鼠标,所以这个状态在手机上基本不存在,这也是移动端设计要额外强调"可点击感"的原因。
- 按下 Active / Pressed正在按住的瞬间。通常颜色变深、稍微内缩、或者加一层水波纹。这个状态给的是"物理确认感"——像真实按键被压下去了。缺了它,界面会显得死板。
- 禁用 Disabled功能暂时不可用。表现是变灰、降低透明度,并且完全不响应点击和悬浮。禁用比"隐藏"更友好:它让用户知道这个功能存在,只是条件还不满足。
- 焦点 Focus键盘当前选中的是它。表现是一圈外发光或轮廓线。这个状态对鼠标用户几乎无感,但对纯键盘用户和屏幕阅读器用户是生命线——没有焦点样式,他们就完全不知道自己在哪。千万不要为了"好看"把它去掉。
除这五个之外,很多控件还有更多状态:加载中(Loading)、选中(Selected / Checked)、错误(Error / Invalid)、只读(Readonly)、展开(Expanded)。而这些状态还会叠加——"禁用 + 选中"的勾选框、"错误 + 焦点"的输入框,都得有明确的样子。
所以做一个看起来简单的按钮,真正的工作量往往不在"默认那张脸",而在剩下那十几种组合。这也是为什么成熟的控件库很有价值:它替你把所有状态组合都推敲过、测试过了。
/* 一个按钮的五种状态(CSS 写法) */
.btn { background:#556b3d; color:#fff; } /* 默认 */
.btn:hover { background:#657d4a; } /* 悬浮 */
.btn:active { background:#44562f; transform:translateY(1px); } /* 按下 */
.btn:focus-visible { outline:2px solid #a05a2c; outline-offset:2px; } /* 焦点 */
.btn:disabled { background:#c9c6bd; cursor:not-allowed; opacity:.6; } /* 禁用 */
回到最开始的那个问题
为什么要花一整节讲控件?因为控件是界面世界的"词汇表"。
你学一门外语,语法再熟,没词汇也说不出话。做界面同理:知道控件有哪几类、每类解决什么问题,你才能在拿到一个需求时立刻反应过来"这里该用下拉选择还是单选组"、"这个提示该用 Toast 还是 Dialog"。选对了控件,界面自然清晰;选错了控件,怎么调样式都别扭。
而更深一层的价值是:控件把界面从"画画"变成了"搭建"。画画依赖天赋,搭建可以靠工程方法。这是界面开发能够规模化、能够协作、能够沉淀设计系统的根本前提。
这一节最核心的概念——控件是"打包好的外观 + 行为"——用装修来讲最透。
假设你要给房子装一扇门。最笨的办法是自己做:买木料、刨平、开榫、拼板、砌门框、装合页、开锁孔、贴密封条。假设你手艺不错,一天能装一扇。要装八扇,八天,而且八扇的手感、缝隙、开合角度多少会有差别。这就是"没有控件"的世界:你在屏幕上画三十个按钮,就得把"画矩形、写文字、判断鼠标在不在里面、按下变色、松手触发"这套动作手写三十遍。
去装修市场买成品门就完全不同。你只说三件事:多宽、什么颜色、往哪边开。剩下所有东西厂里已经做完了:合页承重算好了,锁舌位置对好了,密封条压好了,木料留了膨胀余量所以夏天不卡。你花二十分钟往墙洞里一装就完事。这就是控件:你只声明差异(文字、颜色、点了干什么),共性部分全世界共享一份。
再往深一层,这个类比还解释了三件事。
第一,为什么"外观和行为必须打包"。成品门卖给你的不只是一块板,还包括"怎么转、转多少度会挡住、关上会不会响、锁上以后从外面推不动"这一整套行为。只卖板不卖行为,那叫木料,不叫门。同理,只画一个圆角矩形,那叫图形,不叫按钮。
第二,为什么控件要有那么多"状态"。一扇合格的门至少有:关着、半开、全开、锁死、把手被压下的瞬间。少了任何一个状态的处理,用户就会疑惑"它到底关上了没有"。按钮的默认、悬浮、按下、禁用、焦点五张脸,是同一个道理。
第三,为什么不建议自己造。你自己做的门第一天也能开合,但一个月后开始刮地、关不严、锁不上——因为你没考虑木料膨胀、门体下沉、五金磨损。控件也是:能显示能点击只是第一天的事,禁用态、超长文字、深色模式、键盘可达、中文输入法,是接下来三个月的事。成熟控件库的真正价值,从来不在"能用",而在"这些坑它都替你踩过一遍了"。
控件是把外观和行为打包在一起的可复用界面零件,按职责分为输入、展示、容器、导航、反馈五类。
理解任何控件都可以用三要素模型:外观(长什么样)、状态(现在怎么样)、行为(怎么响应你);而三者的流向是单向的——操作改状态,状态定外观。
控件通过容器层层嵌套成控件树,坐标、样式、事件都顺着这棵树传递。
在原生与自绘之间选择,本质是在"融入系统"和"保持品牌"之间做取舍。
最后别忘了:一个控件至少有五张脸——默认、悬浮、按下、禁用、焦点。真正专业的界面,差距往往就藏在这些"非默认状态"里。