Skip to content

Switch

Switch 表达会立即生效的开启/关闭设置,例如推送通知或使用分析。批量选择、选择列表与部分选中状态请使用 Checkbox。它是受控组件,不保存内部业务状态,也不是表单框架。

基础用法

正在加载组件预览...

.checked(...) 是当前受控值而非初始值。一次有效激活只将 !checked 传给 on_change;宿主在回调中更新状态并调用 cx.notify(),下一次 render 再传回新值。回调是同步本地 callback,不是运行时事件总线;异步工作由宿主在回调中启动和管理。.loading(...) 同样是受控输入,不会自行启动任务或改变 checked。

常用状态

正在加载组件预览...

Anatomy

text
根交互与焦点区域
├─ track
│  ├─ thumb 槽(关闭时逻辑起始侧,开启时逻辑末端侧)
│  └─ 状态内容槽(显示当前状态,位于 thumb 腾出的另一侧)
└─ 可选 trailing label

track、thumb 与 label 只有一个交互目标、一个 Tab stop 和一个可访问节点。label 可在窄容器中换行,track 不会压缩。

没有配置状态内容时,Switch 保持原有紧凑 track。配置任意一侧后进入内容模式,统一使用 24px track 和 20px thumb,不因内容形式或语义尺寸改变高度。轨道由一个 thumb 槽和一个共享内容槽组成;内容槽宽度取 checked 与 unchecked 两种内容类型所需宽度的较大值,因此切换时轨道不会跳动。四种默认尺寸的文字槽都至少容纳两个中文全角字并保留 2px 余量,文档推荐的“开启/关闭”不会被意外截断。状态内容与外侧轨道边缘之间保留主题化间距;纯图标只占图标宽度,不会被强行撑成文字宽度。checked 内容显示在逻辑起始侧,unchecked 内容显示在逻辑末端侧。

API

API说明
Switch::new(id)创建稳定 ElementId 的 Switch,默认关闭且可用。
.checked(bool)设置当前受控值。
SwitchContent::text(text)创建纯文字状态内容。
SwitchContent::icon(icon)通过 IntoIconSource 创建纯装饰图标内容。
SwitchContent::icon_text(icon, text)创建图标在前、文字在后的状态内容。
.checked_content(content)设置开启状态的轨道内容,重复调用时最后一次生效。
.unchecked_content(content)设置关闭状态的轨道内容,重复调用时最后一次生效。
.loading(bool)在 thumb 内显示 spinner,阻止激活但保留焦点与 Tab 停靠。
.disabled(bool)禁用鼠标、Enter、Space 和正常 Tab 停靠。
.transition_duration(Duration)设置下一次 checked 切换的时长;默认 180ms,Duration::ZERO 直接切换。
.label(text)设置 trailing 可见 label,也是默认可访问名称。
.size(ComponentSize)显式 XsSmMdLg 尺寸。
.cursor_style(CursorStyle)设置空闲可用光标;loading 使用 Arrow,disabled 始终优先。
.aria_label(text)覆盖或提供可访问名称。
.aria_description(text)提供补充无障碍描述。
.on_change(handler)接收下一 bool 值、WindowApp,不携带 ClickEvent
.on_change_in(cx, handler)绑定宿主 Entity 的状态变化回调。
.on_click(handler)标准原始激活入口,适合先启动后台请求。
.on_click_in(cx, handler)将标准激活入口绑定到宿主 Entity。
.on_focus / .on_blur注册真实焦点转换回调。
.on_focus_in / .on_blur_in注册 Entity 绑定的焦点回调。

Switch 实现 Changeable<bool>ClickableDisableableFocusableSizableon_clickon_change 共用一个激活 handler 槽,连续配置时后调用者生效,不会在一次激活中重复调用两套回调。

键盘、焦点与无障碍

可用且非 loading 的 Switch 进入正常 Tab 顺序,Space 在 keyup 时切换,Enter 不激活;带 Ctrl、Alt、Shift 或 Meta 的 Space 不切换。鼠标点击 track、thumb 或 label 都只调用一次回调。loading 会消费鼠标、Enter 和 Space,避免重复提交或冒泡到父元素,但仍可通过 Tab 聚焦并保留 focus-visible。disabled 同样不激活,并退出正常 Tab 顺序;disabled + loading 使用 disabled 的颜色、光标和焦点规则,同时继续显示 spinner。

根节点使用 Role::Switch,并将关闭映射为 Toggled::False、开启映射为 Toggled::True,从不产生 mixed。.aria_label(...) 覆盖可见 label;没有可见 label 时必须提供它。disabled 状态使用 disabled 视觉与不可操作光标。

轨道状态内容只补充视觉状态:图标是装饰性的,不创建新的可访问节点或 Tab stop;“开启/关闭”也不会替代业务名称。.label("通知").aria_label("通知") 仍负责提供 Switch 的可访问名称。

正在加载组件预览...

checked 状态和焦点生命周期彼此独立:重绘、builder 值变化和焦点转换都不会自行触发 on_change;checked 改变也不会伪造 focus/blur。_in 表示 Context::listener 的 Entity 绑定,Entity 销毁后会安全 no-op。

Loading 与受控任务

正在加载组件预览...

尺寸与状态内容

正在加载组件预览...
正在加载组件预览...

loading spinner 固定在 thumb 内,不改变 thumb 或 track 尺寸;thumb 仍位于当前 checked 对应的位置,轨道内容继续表达该状态。spinner 使用独立的稳定动画 ID 和固定循环周期,因此 .transition_duration(...) 不会改变或重启它。reduced-motion 下显示静态帧,不持续请求动画帧。

宿主可以在请求开始时先乐观更新 checked,也可以保持 checked,等请求成功后再更新;失败提示、回滚、取消与任务生命周期都由宿主负责。.loading(false) 后恢复正常鼠标与 Space 激活。

需要以后端结果为准时,使用 on_click_in 读取宿主当前值并启动请求,不要立即修改 checked;请求期间由宿主传入 loading(true),成功后再写入服务器确认的 checked,失败时保持原值并显示业务错误。若直接使用 on_change_in,回调会同时收到建议的下一布尔值。两种入口是替代关系,后调用者生效。

主题、尺寸与限制

四种语义尺寸保留各自的紧凑 track、命中区域、图标、内容宽度、spinner、间距和排版 token;进入内容模式后统一为 24px track 与 20px thumb。紧凑模式尺寸不变。Light、Dark 与 System 模式通过 Vektra 主题解析;normal、hover、pressed、focus-visible 和 disabled 均使用主题 token。loading 不显示误导性的 hover/pressed 反馈。旧主题未提供新增内容或 loading token 时使用语义 fallback;一旦开始提供其中一组新增 token,就必须完整覆盖该组的两种视觉状态或四种尺寸。

Switch 在 macOS、Windows、Linux 与 Web 预览中复用同一 GPUI 实现。窄容器的换行和可用宽度由宿主布局负责;状态文字会保持单行并按主题上限截断,平台差异仅来自系统字体、焦点遍历与输入映射。

受控 checked 值变化时,thumb 与内容默认使用 180ms、固定 ease-out cubic;旧内容在前半程淡出,新内容在后半程淡入,避免与移动中的 thumb 明显重叠。.transition_duration(...) 接受调用方传入的非零时长且不静默夹取,建议 100–400ms;Duration::ZERO 不创建状态切换动画。初次 render 不播放入场动画,只改变 duration 不会增加 motion generation 或重启动画,同一次 render 同时改变 checked 与 duration 时使用新时长。GPUI reduced-motion 的优先级更高,会让 thumb、内容与 spinner 直接显示静态终态。

  • 没有非受控状态或 default_checked
  • 状态文字保持单行;四种默认尺寸支持“开启/关闭”这类两字中文短状态,更长内容仍按主题上限截断。
  • 没有拖拽、indeterminate、自定义 easing/复杂动画配置、任意 AnyElement slot 或表单校验。
  • 如果两个选项需要始终同时可见并可分别点击,应使用 Segmented Control,而不是扩展 Switch。
  • label 固定在 track 后方。
  • 桌面示例可运行:cargo run --example switch

性能契约

  • 标准负载为 100 个可见 Switch;构建、状态解析、布局与绘制为 O(1)。
  • transition/loading Task 由 keyed state 拥有,状态替换或 owner 移除后必须释放;无无界历史或缓存。
  • 10K/100K 同时可见通过 VirtualList 解决;基准位于 component_wall