12 KiB
Segmented 分段控制器
概述
分段控制器(Segmented)用于在少量互斥选项之间进行快速切换。它在视觉上提供比 RadioButton 更强的操作感和辨识度,类似于 iOS/macOS 的 SegmentedControl。用户通过点击不同的分段来切换当前选中项,被选中的分段通过一个带有投影的**滑动色块(Thumb)**来标识,选中态切换时色块会平滑动画滑动到目标位置。
AtomUI 的 Segmented 控件完整复刻了 Ant Design 5.0 Segmented 的设计规范,在 .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 ← 文字/自定义内容