资讯动态

gpui-kit Radio 单选按钮组件完全指南:互斥选择、RadioGroup 分组与受控状态管理

发布时间:2026/9/15 18:05:38 来源:尧图企业网站定制
gpui-kit Radio 单选按钮组件完全指南互斥选择、RadioGroup 分组与受控状态管理【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kitRadio单选按钮是界面中最经典的「多选一」控件同一时刻一组选项中只能有一个处于选中状态。在基于 GPUI 构建跨平台桌面应用的 Rust 组件库 gpui-kit 中Radio与RadioGroup是完成设置项、问卷、支付方式选择等互斥场景的标准组件。本文以 website/zh-CN/component/radio.md 为骨架结合 crates/component/src/radio.rs 与 crates/base/src/radio.rs 的源码实现系统讲解 Radio 的受控状态管理、on_change回调机制、RadioGroup 分组用法、尺寸与禁用状态以及无障碍Accessibility与焦点管理细节。读完本文你将能够在 gpui-kit 项目中正确、规范地落地任意单选场景。组件定位与设计理念Radio 组件在 gpui-kit 中分两层实现基础层primitivecrates/base/src/radio.rs 中的Radio与 crates/base/src/radio_group.rs 中的RadioGroup负责激活activation、焦点focus、无障碍accessibility行为但不携带任何样式——指示器、标签、布局、颜色都由应用层自己绘制组件层componentcrates/component/src/radio.rs 中的Radio与RadioGroup在基础层之上封装了主题色、尺寸、标签文本、Tooltip 等开箱即用的外观是日常开发直接使用的对象。组件层Radio的结构体crates/component/src/radio.rs#L19-L37封装了base: BaseRadio基础单选按钮、style: StyleRefinement、label: OptionText、children: VecAnyElement、checked、disabled、tab_stop、tab_index、size、on_click回调、tooltip以及无障碍相关的position_in_set/size_of_set等字段。可以看到组件层通过组合基础层完成交互逻辑自己只负责视觉与主题。从组件层导出关系看crates/component/src/lib.rs#L63 声明了pub mod radio;而基础层在 crates/base/src/lib.rs#L139-L140 中pub use radio::{Radio, RadioStyles}; pub use radio_group::RadioGroup;两个层级均对外可用。导入在 gpui-kit 中使用 Radio 组件从组件层导入即可use gpui_kit::component::radio::{Radio, RadioGroup};若你只需要无样式的基础控件例如自行设计整套视觉方案也可以从gpui_base导入基础层类型use gpui_base::{Radio as BaseRadio, RadioGroup as BaseRadioGroup};组件层的Radio正是这样使用的见 crates/component/src/radio.rs#L13。基础用法基础单选按钮最简单的用法是直接构造一个Radio设置 ID、标签、选中状态并通过on_change接收状态变化请求Radio::new(radio-option-1) .label(Option 1) .checked(false) .on_change(|checked, _, _| { println!(Radio is now: {}, checked); })注意这里on_change的回调签名是Fn(bool, mut Window, mut App)第一个参数是请求的新选中状态。为什么叫「请求」因为从源码看这是一个受控组件组件层Radio::on_change的实现crates/component/src/radio.rs#L120-L123只是把回调存入on_click: OptionRcdyn Fn(bool, mut Window, mut App)渲染时crates/component/src/radio.rs#L268-L273才把事件转发出去真正决定「选中态是否生效」的是调用方。基础层的语义更为直白crates/base/src/radio.rs#L124-L134点击一个未选中的 Radio 会通过on_change请求true而已选中的 Radio 再次激活是空操作——因为单选按钮无法取消选中自身。受控单选按钮状态由 View 持有正确的受控写法是把选中状态存放在视图结构体中回调里更新状态并调用cx.notify()触发重绘struct MyView { radio_checked: bool, } impl Render for MyView { fn render(mut self, _: mut Window, cx: mut ContextSelf) - impl IntoElement { Radio::new(radio) .label(Select this option) .checked(self.radio_checked) .on_change(cx.listener(|view, checked, _, cx| { view.radio_checked *checked; cx.notify(); })) } }cx.listener会把回调绑定到视图实体上*checked解引用拿到新的bool后写入状态cx.notify()触发重绘。如果省略cx.notify()界面上的选中态不会随点击更新——这是 GPUI 受控组件的通用约定Radio 亦不例外。RadioGroup推荐当选项多于一个时官方文档明确推荐使用RadioGroup而不是手动维护一组独立的Radiostruct MyView { selected_option: Optionusize, } impl Render for MyView { fn render(mut self, _: mut Window, cx: mut ContextSelf) - impl IntoElement { RadioGroup::horizontal(options) .children([Option 1, Option 2, Option 3]) .selected_index(self.selected_option) .on_change(cx.listener(|view, selected_index: usize, _, cx| { view.selected_option Some(*selected_index); cx.notify(); })) } }RadioGroup的on_change回调参数是选中项的索引usize。从源码实现看组件层RadioGroupcrates/component/src/radio.rs#L385-L418在渲染时会用selected_ix Some(ix)决定每个子Radio的checked把每个子 Radio 的id重写为ix.into()位置索引确保每个 Radio 拥有独立的元素身份通过set_position基础层见 crates/base/src/radio.rs#L145-L151为每个 Radio 注入position_in_set ix 1与size_of_set total让读屏软件能够播报「第 2 项共 5 项」把分组的on_click回调包装成每个子项的点击回调参数固定为当前索引ix。同时RadioGroup::children与child的入参都支持impl IntoRadio——static str、String、SharedString会通过 crates/component/src/radio.rs#L367-L383 的From实现自动转换为「以文本作为 ID 和标签」的Radio这也是children([Option 1, ...])能直接传字符串数组的原因。不同尺寸Radio实现了Sizabletrait可指定四种尺寸Radio::new(small).label(Small).xsmall() Radio::new(medium).label(Medium) Radio::new(large).label(Large).large()默认尺寸是Size::Medium。从渲染逻辑看crates/component/src/radio.rs#L168-L173尺寸通过rems()换算为指示器圆点的边长XSmall0.75remSmall0.875remLarge1.125remMedium默认1rem同时 crates/component/src/radio.rs#L219-L225 会按尺寸切换文本字号text_xs()/text_sm()/text_base()/text_lg()使指示器与标签文字保持视觉协调。禁用状态禁用既可以是单选态disabled也可以叠加选中态Radio::new(disabled) .label(Disabled option) .disabled(true) .checked(false) Radio::new(disabled-checked) .label(Disabled and checked) .checked(true) .disabled(true)组件层的禁用渲染逻辑crates/component/src/radio.rs#L190-L194会把边框色与背景色统一降到opacity(0.5)并把标签文字切换为muted_foreground弱化色crates/component/src/radio.rs#L256-L258。更重要的行为保证在基础层crates/base/src/radio.rs#L227-L234 中回调只在!disabled !checked时才被挂载到on_click上——也就是说禁用或已选中的 Radio 完全不响应点击。对应的测试checked_and_disabled_radios_are_inertcrates/base/src/radio.rs#L372-L380验证了对(checked, disabled)分别为(true, false)与(false, true)的控件执行鼠标点击与enter/space键盘操作回调计数始终为 0。此外 crates/base/src/radio.rs#L220-L226 显示禁用状态下控件不再track_focus即无法获得键盘焦点。多行标签与自定义内容Radio实现了ParentElement可以通过.child(...)追加任意子元素实现多行标签与补充说明文字Radio::new(custom) .label(Primary option) .child( div() .text_color(cx.theme().muted_foreground) .child(This is additional descriptive text that provides more context.) ) .w(px(300.))渲染时crates/component/src/radio.rs#L246-L264标签与子内容被包裹在v_flex容器中行高1.25、子元素间距gap_1标签在上、补充内容在下形成多行排版。圆点指示器则会通过 crates/component/src/radio.rs#L232 的mt(indicator_size * 0.125)微调垂直位置确保在多行标签场景下指示器仍对齐首行文本中心items_start配合相对定位。在 crates/story/src/stories/radio_story.rs#L76-L87 的交付方式示例中可以看到同样的模式每个选项的主标签下用text_xs()muted_foreground挂一行说明如 Arrives in 3–5 business days.并借助.with_size(self.size)让整组选项跟随工具栏切换尺寸。自定义 Tab 顺序Radio 默认参与 Tab 聚焦tab_stop truetab_index 0也支持自定义Radio::new(radio) .label(Custom tab order) .tab_index(2) .tab_stop(true)组件层Radio::new的默认值crates/component/src/radio.rs#L52-L53为tab_index: 0、tab_stop: true这两个值最终会透传到基础层由基础层在渲染时crates/base/src/radio.rs#L220-L226作用于焦点句柄focus_handle.tab_index(self.tab_index).tab_stop(self.tab_stop)。此外组件层还实现了FocusableExtcrates/component/src/radio.rs#L133-L142提供focus_ring(enabled)控制聚焦光环的显隐聚焦且启用光环时crates/component/src/radio.rs#L216-L218会调用focus_ring_style绘制主题化的焦点轮廓。RadioGroup 用法横向布局RadioGroup::horizontal(horizontal-group) .children([First, Second, Third]) .selected_index(Some(0)) .on_change(cx.listener(|view, index, _, cx| { println!(Selected index: {}, index); cx.notify(); }))horizontal(id)等价于Self::new(id).layout(Axis::Horizontal)crates/component/src/radio.rs#L310-L312。组件层渲染时crates/component/src/radio.rs#L391-L395横向布局使用h_flex().w_full().flex_wrap()——宽度撑满且允许换行避免选项过多时溢出。纵向布局RadioGroup::vertical(vertical-group) .child(Radio::new(option1).label(United States)) .child(Radio::new(option2).label(Canada)) .child(Radio::new(option3).label(Mexico)) .selected_index(Some(1)) .disabled(false)vertical(id)与new(id)等价crates/component/src/radio.rs#L305-L307因为new的默认布局就是Axis::Verticalcrates/component/src/radio.rs#L297。纵向布局使用v_flex每个子 Radio 独占一行适合选项文本较长的场景。selected_index(Some(1))表示默认选中第 2 项索引从 0 开始传None表示初始不选中任何项。带样式的分组RadioGroup同样实现了Styledtrait可以对整个分组直接施加 GPUI 样式RadioGroup::vertical(styled-group) .w(px(220.)) .p_2() .border_1() .border_color(cx.theme().border) .rounded(cx.theme().radius) .child(Radio::new(option1).label(Option 1)) .child(Radio::new(option2).label(Option 2)) .child(Radio::new(option3).label(Option 3)) .selected_index(Some(0))样式会通过refine_style(self.style)crates/component/src/radio.rs#L400作用在分组的容器上。注意RadioGroup::new的默认样式带有flex_1()crates/component/src/radio.rs#L295因此在某些布局中它倾向于占满可用宽度。另外组件层RadioGroup内部还通过base.gap_3()crates/component/src/radio.rs#L402保证子项之间统一的0.75rem间距。禁用整个分组RadioGroup::vertical(disabled-group) .children([Option A, Option B, Option C]) .selected_index(Some(1)) .disabled(true)RadioGroup::disabled(true)会把禁用状态传播给组内所有子Radio渲染循环中每个子项都会调用radio.disabled(disabled)crates/component/src/radio.rs#L409从而一次性禁用整组。已选中的那项会以半透明选中态呈现未选中的则不可点击。API 参考Radio组件层Radiocrates/component/src/radio.rs#L39-L124的主要方法方法说明new(id)使用给定 ID 创建单选按钮ID 类型为impl IntoElementIdlabel(text)设置标签文本类型为impl IntoTextchecked(bool)设置选中状态默认falsedisabled(bool)设置禁用状态默认falseon_change(fn)请求选中状态变化回调参数为新的bool受控模式必须由调用方保存状态并cx.notify()on_click(fn)on_change的兼容别名与on_change共享同一个回调槽位最后一次设置生效tab_stop(bool)是否允许通过 Tab 聚焦默认truetab_index(isize)设置 Tab 顺序默认0accessibility_label(text)覆盖读屏软件播报的名称可见标签不变默认取自labeltooltip(text)设置悬停提示文本focus_ring(bool)控制聚焦光环是否启用默认true关于on_change与on_click组件层两者的实现互相委托crates/component/src/radio.rs#L109-L123共用同一个on_click字段因此链式调用时后设置者覆盖前者而不会同时触发两个回调。RadioGroup组件层RadioGroupcrates/component/src/radio.rs#L290-L359的主要方法方法说明new(id)创建默认纵向布局、未选中任何项的分组horizontal(id)创建横向分组等价于new(id).layout(Axis::Horizontal)vertical(id)创建纵向分组等价于new(id)layout(Axis)显式设置布局方向取值Axis::Horizontal/Axis::Verticalchild(Radio)追加单个 Radio入参为impl IntoRadiochildren(items)通过迭代器批量追加 Radio字符串数组可直接传入selected_index(Optionusize)设置选中项索引None表示不选中disabled(bool)禁用分组内所有 Radioon_change(fn)选择变化回调参数为选中项的usize索引on_click(fn)on_change的兼容别名规则同Radio样式Radio与RadioGroup都实现了 GPUI 的Styledtrait支持.w()、.p_2()、.border_1()、.text_color()、.rounded()等全部样式方法Radio额外实现了Sizabletraitcrates/component/src/radio.rs#L126-L131xsmall()超小尺寸small()小尺寸medium()中尺寸默认值large()大尺寸with_size(size)通过变量如Size::Large动态设置尺寸无障碍与键盘交互Radio 在 gpui-kit 中并非简单的「圆形 点击」控件底层交互与无障碍语义是经过专门设计的主要体现于基础层实现1. 语义角色与状态crates/base/src/radio.rs#L201-L219渲染为Role::RadioButton同时设置aria_toggled与aria_selected——注释说明不同读屏软件分别读取其中一种因此两者都设置。分组容器则声明Role::RadioGroup与aria_orientation横向/纵向见 crates/base/src/radio_group.rs#L56-L66。2. 组内定位播报通过position_in_set/size_of_set输出aria_position_in_set与aria_size_of_set读屏软件可播报「第 N 项共 M 项」。使用RadioGroup时这一信息自动注入无需手工设置。3. 键盘操作聚焦后可通过空格键space与回车键enter激活。测试pointer_and_keyboard_activation_fire_oncecrates/base/src/radio.rs#L351-L370验证了鼠标点击触发 1 次回调随后enter与space各触发 1 次共 2 次且ClickEvent::Keyboard(_)能正确区分键盘来源。4. 显式无障碍名称组件层提供accessibility_label方法当可见标签无法准确表达语义时例如纯图标 Radio用它覆盖播报名称而不改变显示内容对应测试见 crates/component/src/radio.rs#L424-L448。最佳实践互斥选项优先使用RadioGroup不要手动管理一组独立的Radio——分组会自动处理互斥选中、索引回调与组内无障碍定位信息。标签要明确用户应当一眼看懂每个选项的含义必要时用.child(...)补充说明文字。对必填项提供合理的默认选中项避免空状态歧义可省略时传selected_index(None)。选项顺序应符合业务逻辑例如频率、重要性或字母顺序。单选项数量应保持适中通常建议 2 到 7 个超过时考虑改用其他选择控件如下拉选择。多组单选项应配合清晰标题和视觉分组如 crates/story/src/stories/radio_story.rs 中「Delivery」「Billing cycle」两组的呈现方式。选项较少时可横向排列horizontal较多或文本较长时更适合纵向排列vertical。始终走受控模式回调中写入状态并调用cx.notify()否则 UI 与数据会脱节禁用项不会触发回调无需在回调里重复判断。需要自定义外观时直接在组件层Radio/RadioGroup上叠加Styled方法若需要完全从零设计交互如自定义状态样式可下沉到gpui_base的基础层并配合其RadioStyleschecked/disabled语义样式见 crates/base/src/radio.rs#L45-L57使用。【免费下载链接】gpui-kitRust GUI components for building fantastic cross-platform desktop application by using GPUI.项目地址: https://gitcode.com/GitHub_Trending/gp/gpui-kit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价