# Segmented 分段控制器 ## 概述 分段控制器(Segmented)用于在少量互斥选项之间进行快速切换。它在视觉上提供比 RadioButton 更强的操作感和辨识度,类似于 iOS/macOS 的 SegmentedControl。用户通过点击不同的分段来切换当前选中项,被选中的分段通过一个带有投影的**滑动色块(Thumb)**来标识,选中态切换时色块会平滑动画滑动到目标位置。 AtomUI 的 `Segmented` 控件完整复刻了 [Ant Design 5.0 Segmented](https://ant.design/components/segmented-cn) 的设计规范,在 .NET / Avalonia 平台上提供与 Ant Design React 版一致的视觉体验和交互行为。 --- ## 设计原理 ### Ant Design 的分段控制器设计哲学 Ant Design 对 Segmented 的定位是:**「分段控制器。当用户需要在几个互斥的选项之间进行选择时使用,相比 Radio 按钮有更强的视觉感知」**。典型应用场景包括: - 视图模式切换(列表视图 / 卡片视图 / 网格视图) - 时间维度切换(日 / 周 / 月 / 季 / 年) - 数据分类筛选(全部 / 待处理 / 已完成) - 简单的标签页导航 **核心设计特征**: | 特征 | 说明 | |---|---| | 🔘 **互斥单选** | 始终有且仅有一个选项被选中,首次渲染默认选中第一项 | | 🎯 **滑动色块** | 选中项通过一个独立的色块(Thumb)标识,切换时色块平滑滑动到新位置 | | 📏 **三种尺寸** | Large / Middle / Small,与全局尺寸体系一致 | | 🔲 **Block 模式** | 可撑满父容器,选项均分宽度 | | 🖼️ **图标支持** | 每个选项可包含图标,或仅显示图标 | | 🚫 **逐项禁用** | 可单独禁用某些选项 | ### Avalonia 基础能力 AtomUI 的 `Segmented` 控件基于 Avalonia 的 `SelectingItemsControl`。理解 Avalonia 提供的基础能力有助于理解 AtomUI 在其之上做了哪些扩展。 **Avalonia SelectingItemsControl 的核心职责:** `SelectingItemsControl` 继承自 `ItemsControl`,增加了选择管理能力。它维护 `SelectedItem` / `SelectedIndex` 等选择状态,并通过 `SelectionChanged` 事件通知外部。其继承链为: ``` Control → TemplatedControl → ItemsControl → SelectingItemsControl ``` **Avalonia SelectingItemsControl 提供的基础属性:** | 属性 | 说明 | |---|---| | `Items` | 数据项集合(ItemsControl 基础能力) | | `ItemTemplate` | 数据项的 DataTemplate | | `SelectedIndex` | 当前选中项的索引 | | `SelectedItem` | 当前选中的数据对象 | | `SelectionMode` | 选择模式(Segmented 固定为 `Single`) | | `SelectionChanged` | 选中项变化事件 | | `AutoScrollToSelectedItem` | 选中时是否自动滚动到选中项(Segmented 默认 `false`) | ### AtomUI 的扩展设计 AtomUI `Segmented` 在 Avalonia SelectingItemsControl 的基础上,遵循 Ant Design 5.0 规范做了以下扩展: | 扩展能力 | 实现方式 | 设计动机 | |---|---|---| | **滑动色块(Thumb)** | 自定义 `Render` 方法绘制带圆角和阴影的矩形 | 复刻 Ant Design 的选中滑块动画效果 | | **色块滑动动画** | `SelectedThumbPos` / `SelectedThumbSize` + `PointTransition` / `SizeTransition` | 选中项切换时色块平滑过渡 | | **三种尺寸** | `ISizeTypeAware` 接口 + `SizeType` 共享属性 | 全局统一的 Large / Middle / Small 尺寸系统 | | **Block 模式** | `IsExpanding` 属性 + `SegmentedStackPanel` 自定义布局 | 选项均分父容器宽度 | | **图标支持** | `SegmentedItem.Icon` 属性 + `IconPresenter` | 每个选项可包含 Ant Design 图标 | | **逐项禁用** | 各 `SegmentedItem` 独立的 `IsEnabled` 属性 | 灵活控制可用性 | | **默认选中首项** | `OnApplyTemplate` 中检查并设置 `SelectedIndex = 0` | 对齐 Ant Design 默认行为 | | **表单集成** | `IFormItemAware` 接口 | 可参与 FormItem 验证流程 | | **Design Token** | `SegmentedToken` + `RegisterTokenResourceScope` | 所有视觉值从全局/组件级 Token 派生 | | **过渡动画** | `IMotionAwareControl` + `IsMotionEnabled` | 色块滑动、选项背景色过渡动画可全局开关 | --- ## 功能详解 ### 滑动色块(Selected Thumb) Segmented 最具标志性的视觉特征是**滑动色块**——一个在选中项后方渲染的带圆角、带阴影的矩形色块。它的行为如下: - **位置追踪**:通过 `SelectedThumbPos` 和 `SelectedThumbSize` 记录当前选中项的位置和尺寸 - **平滑动画**:当选中项变化时,`SelectedThumbPos` 通过 `PointTransition` 动画过渡到新位置,`SelectedThumbSize` 通过 `SizeTransition` 动画过渡到新尺寸 - **自定义渲染**:在 `Render` 方法中使用 `DrawRectangle` 直接绘制色块和容器背景,无需额外的视觉层 - **阴影效果**:色块带有 `BoxShadowsTertiary` 投影,增加立体感 ### Block 模式(IsExpanding) 当 `IsExpanding = true` 时: - Segmented 容器撑满父元素宽度(`HorizontalAlignment = Stretch`) - 所有选项**均分**容器宽度 - 使用 `SegmentedStackPanel`(自定义 Panel)计算等宽布局 ### 三种尺寸(SizeType) 尺寸通过 `SizeType` 属性设置,影响容器圆角、选项高度、字体大小和内间距: | 尺寸 | 容器圆角 | 色块圆角 | 选项最小高度 | 字体大小 | 选项内间距 | |---|---|---|---|---|---| | `Large` | `BorderRadiusLG` | `BorderRadius` | `ControlHeightLG - TrackPadding * 2` | `FontSizeLG` | `SegmentedItemPadding` | | `Middle` | `BorderRadius` | `BorderRadiusSM` | `ControlHeight - TrackPadding * 2` | `FontSize` | `SegmentedItemPadding` | | `Small` | `BorderRadiusSM` | `BorderRadiusXS` | `ControlHeightSM - TrackPadding * 2` | `FontSize` | `SegmentedItemPaddingSM` | ### 图标支持 每个 `SegmentedItem` 支持 `Icon` 属性(`PathIcon` 类型),可以: - 仅图标(不设 Content) - 图标 + 文字(同时设置 Icon 和 Content) 当 `Icon` 不为 `null` 时,`:has-icon` 伪类被激活,文字内容会增加左边距以与图标保持间距。 ### 过渡动画 | 控件 | 动画属性 | 说明 | |---|---|---| | `Segmented` | `SelectedThumbPos`、`SelectedThumbSize` | 色块位置和尺寸的平滑过渡 | | `SegmentedItem` | `Background` | 选项背景色的平滑过渡(悬浮/按下态变化) | --- ## 与 Ant Design 规范的对齐程度 | 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 | |---|---|---|---| | 基本选项 `options` | ✅ `string[] \| SegmentedOption[]` | ✅ `SegmentedItem` 子项 | ✅ 完全对齐 | | 选中项 `value` | ✅ 受控属性 | ✅ `SelectedItem` / `SelectedIndex` | ✅ 完全对齐 | | 默认选中 `defaultValue` | ✅ 默认值 | ✅ 默认选中首项 | ✅ 完全对齐 | | Block 模式 `block` | ✅ 布尔属性 | ✅ `IsExpanding` 属性 | ✅ 完全对齐 | | 三种尺寸 `size` | ✅ `large / middle / small` | ✅ `SizeType` 属性 | ✅ 完全对齐 | | 禁用选项 `disabled` | ✅ 逐项 / 整体禁用 | ✅ `SegmentedItem.IsEnabled` | ✅ 完全对齐 | | 图标 `icon` | ✅ ReactNode | ✅ `PathIcon` 属性 | ✅ 对齐(类型不同,语义一致) | | 选中滑块动画 | ✅ CSS transition | ✅ `PointTransition` + `SizeTransition` | ✅ 完全对齐 | | 自定义渲染 `label` | ✅ ReactNode | ⚠️ 通过 `ContentTemplate` 部分支持 | ⚠️ 部分对齐 | | 选项变化回调 `onChange` | ✅ 回调函数 | ✅ `SelectionChanged` 事件 | ✅ 完全对齐 | --- ## 继承关系 ### Segmented ``` Avalonia.Controls.Primitives.TemplatedControl └── Avalonia.Controls.ItemsControl └── Avalonia.Controls.SelectingItemsControl └── AtomUI.Controls.Commons.AbstractSegmented ├── implements ISizeTypeAware ├── implements IMotionAwareControl ├── implements IFormItemAware └── AtomUI.Desktop.Controls.Segmented ``` ### SegmentedItem ``` Avalonia.Controls.Primitives.TemplatedControl └── Avalonia.Controls.ContentControl └── AtomUI.Controls.Commons.AbstractSegmentedItem (ISelectable) └── AtomUI.Desktop.Controls.SegmentedItem ``` **各层级职责划分:** | 层级 | 提供的能力 | |---|---| | `SelectingItemsControl` (Avalonia) | 数据项管理、选择状态(`SelectedItem` / `SelectedIndex`)、`SelectionChanged` 事件、容器化(Container Virtualization) | | `AbstractSegmented` (AtomUI.Controls) | 滑动色块渲染与动画、`SizeType` / `IsExpanding` / `IsMotionEnabled` 属性、默认选中首项、`SegmentedItem` 容器准备(传递 SizeType/IsMotionEnabled)、表单集成(IFormItemAware) | | `Segmented` (AtomUI.Desktop.Controls) | Token 作用域注册、创建桌面端 `SegmentedItem` 容器 | | `ContentControl` (Avalonia) | 任意内容容纳、`Content` / `ContentTemplate` | | `AbstractSegmentedItem` (AtomUI.Controls) | `IsSelected` / `Icon` 属性、`:pressed` / `:selected` / `:has-icon` 伪类、鼠标选择交互、背景色过渡动画 | | `SegmentedItem` (AtomUI.Desktop.Controls) | Token 作用域注册 | **实现的共享接口:** | 接口 | 定义位置 | 作用 | |---|---|---| | `ISizeTypeAware` | `AtomUI.Controls.Shared` | 支持 `SizeType`(Small / Middle / Large)三种尺寸切换 | | `IMotionAwareControl` | `AtomUI.Controls` | 支持 `IsMotionEnabled` 属性,全局动画开关 | | `IFormItemAware` | `AtomUI.Controls.Shared` | 可作为 `FormItem` 的子控件参与表单验证 | | `ISelectable` | Avalonia | 支持 `IsSelected` 属性,参与 `SelectingItemsControl` 选择逻辑 | --- ## 源码位置 ### 基类层(AtomUI.Controls)—— 设备无关 | 文件路径 | 说明 | |---|---| | `src/AtomUI.Controls/Segmented/AbstractSegmented.cs` | 分段控制器抽象基类:滑块渲染/动画、选择逻辑、尺寸/扩展属性、表单集成 | | `src/AtomUI.Controls/Segmented/AbstractSegmentedItem.cs` | 分段选项抽象基类:选择状态、图标、伪类管理、背景色过渡 | | `src/AtomUI.Controls/Segmented/SegmentedPseudoClass.cs` | 共享伪类常量定义(`:has-icon`) | | `src/AtomUI.Controls/Segmented/SegmentedStackPanel.cs` | 自定义布局面板:支持普通水平排列和 Block 等宽排列 | ### 平台层(AtomUI.Desktop.Controls)—— 桌面端具体实现 | 文件路径 | 说明 | |---|---| | `src/AtomUI.Desktop.Controls/Segmented/Segmented.cs` | 桌面端 Segmented:Token 注册、创建 `SegmentedItem` 容器 | | `src/AtomUI.Desktop.Controls/Segmented/SegmentedItem.cs` | 桌面端 SegmentedItem:Token 注册 | | `src/AtomUI.Desktop.Controls/Segmented/SegmentedToken.cs` | 组件级 Design Token | | `src/AtomUI.Desktop.Controls/Segmented/Themes/SegmentedTheme.axaml` | Segmented ControlTheme | | `src/AtomUI.Desktop.Controls/Segmented/Themes/SegmentedItemTheme.axaml` | SegmentedItem ControlTheme | | `src/AtomUI.Desktop.Controls/Segmented/Themes/SegmentedThemes.axaml` | 主题资源字典汇总注册 | | Gallery 示例 | `controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/SegmentedShowCase.axaml` | --- ## 模板结构 ### Segmented 模板 ``` Border#Frame (CornerRadius, Padding, ClipToBounds) └── ItemsPresenter (PART_ItemsPresenter) └── SegmentedStackPanel ← 自定义布局面板 ├── SegmentedItem [0] ├── SegmentedItem [1] ├── SegmentedItem [2] └── ... ``` > **注意**:Segmented 的容器背景和选中色块均通过 `Render` 方法直接绘制(`DrawRectangle`),而非通过模板元素。这是因为色块需要在所有选项的"下方"但在容器背景的"上方"渲染,且需要动画支持。 ### SegmentedItem 模板 ``` Border#Frame (Background, CornerRadius, Padding) └── DockPanel (LastChildFill=True) ├── IconPresenter#IconPresenter ← 图标展示器(可选,Icon 不为 null 时显示) └── ContentPresenter#Content ← 文字/自定义内容 ```