# OptionButtonGroup 选项按钮组 ## 概述 选项按钮组(OptionButtonGroup)是以按钮样式呈现的单选组件,用于在多个互斥选项中选择一个。它将传统单选按钮的逻辑与按钮的视觉形态相结合,适用于数量有限且语义明确的选项集合,如切换视图模式、选择排序方式、切换布局方向等。 AtomUI 的 `OptionButtonGroup` 控件对应 [Ant Design 5.0 Radio.Group](https://ant.design/components/radio-cn) 中 `optionType="button"` 的变体(即按钮样式的单选组),在 .NET / Avalonia 平台上提供与 Ant Design React 版一致的视觉体验和交互行为。 --- ## 设计原理 ### Ant Design 的按钮单选设计哲学 Ant Design 的 Radio.Group 在 `optionType="button"` 模式下,将每个单选选项渲染为按钮外观。与普通单选按钮(圆形勾选框)相比,按钮样式的单选组更加紧凑、视觉权重更高,适合放置在工具栏或控制面板中。 **两种按钮样式**: | 样式 | 设计意图 | 视觉表现 | |---|---|---| | 🔲 **Outline(轮廓)** | 默认样式,选中项通过边框颜色和文字颜色区分 | 选中项边框变为主色(`ColorPrimary`),文字变为主色 | | 🔳 **Solid(实色)** | 更强调选中状态,选中项有实色填充背景 | 选中项背景变为主色填充,文字变为白色 | **三种尺寸**(通过 `SizeType` 控制): | 尺寸 | 高度 | 说明 | |---|---|---| | `Large` | 40px | 大号,醒目场景 | | `Middle` | 32px | 中号(默认),常规场景 | | `Small` | 24px | 小号,紧凑布局 | ### Avalonia 基础能力 AtomUI 的 OptionButtonGroup 体系基于 Avalonia 的以下基础能力构建: - **OptionButton**(`AbstractOptionButton`)继承自 `Avalonia.Controls.RadioButton`,获得单选互斥行为、`IsChecked` 状态管理、`Content` 内容呈现等能力 - **OptionButtonGroup**(`AbstractOptionButtonGroup`)继承自 `SelectingItemsControl`,获得 `ItemsSource` 数据驱动、`SelectedItem` / `SelectedIndex` 选中管理、容器化子项(`PrepareContainerForItemOverride`)等能力 ### AtomUI 的实现设计 AtomUI 的 OptionButtonGroup 采用**跨平台两层继承模型**: | 设计能力 | 实现方式 | 设计动机 | |---|---|---| | **两种按钮样式** | `OptionButtonStyle` 枚举 + 伪类驱动样式 | 对齐 Ant Design 的 `buttonStyle`(`solid` / `outline`) | | **三种尺寸** | `ISizeTypeAware` 接口 + `SizeType` 共享属性 | 全局统一的 Large / Middle / Small 尺寸系统 | | **自动位置感知** | `OptionButtonPositionTrait` 枚举 | 自动计算每个按钮的位置(First/Middle/Last/OnlyOne),裁剪圆角 | | **图标支持** | `Icon` 属性(`PathIcon` 类型) | 按钮支持文字 + 图标组合 | | **选中边框绘制(Outline)** | 自定义 `Render` 方法 | Outline 模式下选中项绘制主色边框覆盖 | | **分隔线绘制** | 自定义 `Render` 方法 | 按钮之间绘制细分隔线 | | **点击波纹** | `IWaveSpiritAwareControl` + `WaveSpiritDecorator` | 复刻 Ant Design 的 Wave 点击涟漪效果 | | **过渡动画** | `IsMotionEnabled` + `Transitions` | 背景色/前景色/边框色平滑过渡 | | **表单集成** | `IFormItemAware` 接口 | 可参与 FormItem 验证流程 | | **Design Token** | `OptionButtonToken` + `RegisterTokenResourceScope` | 所有视觉值从全局/组件级 Token 派生 | --- ## 功能详解 ### 按钮样式(OptionButtonStyle) 通过 `ButtonStyle` 属性设置,影响选中项的视觉表现: - **Outline**(默认):选中项使用主色边框 + 主色文字,背景保持透明。Group 容器在选中项位置绘制一个覆盖边框 - **Solid**:选中项使用主色背景填充 + 白色文字。Solid 模式下相邻选中项之间不绘制分隔线 ### 自动位置感知与圆角裁剪 OptionButtonGroup 自动为每个子按钮分配 `GroupPositionTrait`: - `First`:第一个按钮,保留左侧圆角,右侧圆角为 0 - `Middle`:中间按钮,四个圆角均为 0 - `Last`:最后一个按钮,保留右侧圆角,左侧圆角为 0 - `OnlyOne`:唯一按钮,保留全部圆角 ### 分隔线与选中边框 Group 容器的 `Render` 方法负责绘制: 1. **外部统一边框**:整个组的外围边框(`BorderBrush` + `BorderThickness`) 2. **分隔线**:每两个相邻按钮之间绘制垂直分隔线 3. **选中边框(Outline 模式)**:在选中按钮的位置绘制 `SelectedOptionBorderColor` 覆盖边框 ### 数据驱动 OptionButtonGroup 支持两种使用方式: - **声明式子项**:直接在 AXAML 中添加 `OptionButton` 子控件 - **数据绑定**:通过 `ItemsSource` 绑定 `OptionButtonData` 集合,自动生成按钮容器 --- ## 与 Ant Design 规范的对齐程度 | 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 | |---|---|---|---| | 按钮样式单选 `optionType="button"` | ✅ | ✅ `OptionButtonGroup` + `OptionButton` | ✅ 完全对齐 | | Outline 样式 `buttonStyle="outline"` | ✅ | ✅ `ButtonStyle="Outline"` | ✅ 完全对齐 | | Solid 样式 `buttonStyle="solid"` | ✅ | ✅ `ButtonStyle="Solid"` | ✅ 完全对齐 | | 三种尺寸 `size` | ✅ `large / middle / small` | ✅ `SizeType` | ✅ 完全对齐 | | 禁用单个选项 | ✅ `disabled` | ✅ `IsEnabled` | ✅ 完全对齐 | | 禁用整个组 | ✅ `disabled` | ✅ `IsEnabled` | ✅ 完全对齐 | | 选中变更回调 `onChange` | ✅ 函数 | ✅ `OptionCheckedChanged` 事件 | ✅ 完全对齐 | | 图标支持 | ✅ `icon` (5.x) | ✅ `Icon` 属性 | ✅ 完全对齐 | | 默认选中 `defaultValue` | ✅ | ✅ `OptionButton.IsChecked="True"` | ✅ 完全对齐 | | 数据驱动 `options` | ✅ 数组 | ✅ `ItemsSource` + `OptionButtonData` | ✅ 完全对齐 | --- ## 继承关系 ### 跨平台两层模型 ``` AtomUI.Controls (基础层,设备无关) AtomUI.Desktop.Controls (平台层) ────────────────────────────────── ────────────────────────────────── AbstractOptionButton : RadioButton ───► OptionButton : AbstractOptionButton AbstractOptionButtonGroup : SelectingItemsControl ─► OptionButtonGroup : AbstractOptionButtonGroup ``` ### AbstractOptionButton(基础层) ``` Avalonia.Controls.Primitives.TemplatedControl └── Avalonia.Controls.ContentControl └── Avalonia.Controls.Primitives.ToggleButton └── Avalonia.Controls.RadioButton └── AtomUI.Controls.Commons.AbstractOptionButton └── AtomUI.Desktop.Controls.OptionButton ``` ### AbstractOptionButtonGroup(基础层) ``` Avalonia.Controls.Primitives.TemplatedControl └── Avalonia.Controls.ItemsControl └── Avalonia.Controls.Primitives.SelectingItemsControl └── AtomUI.Controls.Commons.AbstractOptionButtonGroup ├── implements ISizeTypeAware ├── implements IWaveSpiritAwareControl ├── implements IFormItemAware └── AtomUI.Desktop.Controls.OptionButtonGroup ``` **各层级职责划分:** | 层级 | 提供的能力 | |---|---| | `RadioButton` | 单选互斥行为、`IsChecked` 状态、`Content` 内容、`GroupName` 分组 | | `SelectingItemsControl` | `ItemsSource` 数据驱动、`SelectedItem` / `SelectedIndex`、容器化管理 | | `AbstractOptionButton` | 位置感知圆角裁剪、`Icon` 图标支持、WaveSpirit 波纹、过渡动画、自定义渲染(边框绘制) | | `AbstractOptionButtonGroup` | 按钮样式(Outline/Solid)、尺寸传播、分隔线绘制、选中边框绘制、位置分配、`OptionCheckedChanged` 事件、表单集成 | | `OptionButton` / `OptionButtonGroup` | Desktop Token 注册(`OptionButtonToken.ScopeProvider`) | **实现的共享接口:** | 接口 | 定义位置 | 作用 | |---|---|---| | `ISizeTypeAware` | `AtomUI.Controls.Shared` | 支持 `SizeType`(Small / Middle / Large)三种尺寸切换 | | `IWaveSpiritAwareControl` | `AtomUI.Controls.Shared` | 支持点击涟漪(Wave)动画效果 | | `IFormItemAware` | `AtomUI.Controls.Shared` | 可作为 `FormItem` 的子控件参与表单验证 | --- ## 源码位置 ### 基础层(AtomUI.Controls) | 文件路径 | 说明 | |---|---| | `src/AtomUI.Controls/OptionButtonGroup/AbstractOptionButton.cs` | 设备无关的选项按钮基类 | | `src/AtomUI.Controls/OptionButtonGroup/AbstractOptionButtonGroup.cs` | 设备无关的选项按钮组基类 | | `src/AtomUI.Controls/OptionButtonGroup/OptionButtonGroupEnums.cs` | `OptionButtonStyle` / `OptionButtonPositionTrait` 枚举 | | `src/AtomUI.Controls/OptionButtonGroup/OptionButtonData.cs` | 数据驱动模型 | | `src/AtomUI.Controls/OptionButtonGroup/OptionButtonPointerEventArgs.cs` | 指针事件参数 | | `src/AtomUI.Controls/OptionButtonGroup/OptionCheckedChangedEventArgs.cs` | 选中变更事件参数 | ### 平台层(AtomUI.Desktop.Controls) | 文件路径 | 说明 | |---|---| | `src/AtomUI.Desktop.Controls/OptionButtonGroup/OptionButton.cs` | 桌面端 OptionButton | | `src/AtomUI.Desktop.Controls/OptionButtonGroup/OptionButtonGroup.cs` | 桌面端 OptionButtonGroup | | `src/AtomUI.Desktop.Controls/OptionButtonGroup/OptionButtonToken.cs` | 组件级 Design Token | | `src/AtomUI.Desktop.Controls/OptionButtonGroup/Themes/OptionButtonTheme.axaml` | OptionButton 主题 | | `src/AtomUI.Desktop.Controls/OptionButtonGroup/Themes/OptionButtonGroupTheme.axaml` | OptionButtonGroup 主题 | | `src/AtomUI.Desktop.Controls/OptionButtonGroup/Themes/OptionButtonBoxThemes.axaml` | 主题汇总注册 | ### Gallery 示例 | 文件路径 | 说明 | |---|---| | `controlgallery/AtomUIGallery/ShowCases/Views/DataEntry/RadioButtonShowCase.axaml` | RadioButton 与 OptionButton 综合范例 | --- ## 模板结构 ### OptionButton 模板 ``` Panel ├── WaveSpiritDecorator (PART_WaveSpirit) ← 点击波纹效果层 └── DockPanel#ContentLayout ← 内容布局 ├── IconPresenter#IconPresenter ← 图标(DockPanel.Dock="Left",可选) └── TextBlock ← 文本内容 ``` ### OptionButtonGroup 模板 ``` Border#Frame ← 外围容器(边框+圆角) └── ItemsPresenter (PART_ItemsPresenter) ← 子项呈现器 └── StackPanel (Horizontal) ← 默认水平排列面板 ├── OptionButton ← 选项按钮 1 ├── OptionButton ← 选项按钮 2 └── ... ``` **布局设计要点:** - **自定义渲染**:OptionButtonGroup 的 `Render` 方法负责绘制外围边框、分隔线和选中边框覆盖,而非依赖模板中的多层 Border - **圆角自动裁剪**:每个 OptionButton 根据在组中的位置自动计算圆角(仅首尾按钮保留外侧圆角) - **分隔线绘制**:使用 `DrawLine` 在相邻按钮间绘制像素对齐的分隔线(`EdgeMode.Aliased`)