Files

202 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# IconButton 图标按钮
## 概述
图标按钮(IconButton)是一种极简的按钮控件,它仅展示一个图标,不包含文本内容。IconButton 常用于工具栏、操作栏、表格行内操作等需要节省空间的场景,用户通过图标的含义即可理解按钮的功能。
AtomUI 的 `IconButton` 是 AtomUI 控件体系中的基础构件,广泛用于其他复合控件的内部(如 Input 的清除按钮、Tag 的关闭按钮、DatePicker 的日历图标按钮、Collapse 的展开/收起按钮等),是按钮家族中最轻量的成员。
---
## 设计原理
### Ant Design 的图标按钮设计
Ant Design 中没有独立的 "IconButton" 组件。图标按钮通常通过两种方式实现:一是 `Button` 组件设置 `shape="circle"` + `icon` 属性;二是组件内部直接渲染可点击的图标区域(如 Input 的后缀清除图标、Modal 的关闭按钮、Tabs 的关闭图标等)。
这种隐式模式导致了两个问题:每个需要图标按钮的组件都要重复实现相同的交互逻辑(悬浮变色、按下反馈、禁用态),且行为不一致。AtomUI 将这一模式抽象为独立的 `IconButton` 控件,使其可以在各种场景中复用,同时保持一致的交互行为。
### Avalonia Button 基础能力
AtomUI 的 `IconButton` 通过 `AbstractIconButton` 继承自 Avalonia 的 `Avalonia.Controls.Button`。理解 Avalonia Button 的基础能力有助于理解 IconButton 的交互基础。
**Avalonia Button 的核心职责:**
Avalonia 的 `Button` 是一个标准的 `ContentControl`,实现了 `ICommandSource` 和 `IClickableControl` 接口。其核心行为是:**响应指针操作,在指针按下时提供视觉按压反馈(`:pressed` 伪类),在指针释放时触发 `Click` 事件**。它的继承链为:
```
Control → TemplatedControl → ContentControl → Button
```
**Avalonia Button 提供的基础属性(IconButton 复用):**
| 属性 | 说明 |
|---|---|
| `Command` / `CommandParameter` | MVVM 命令绑定,点击时调用 `ICommand.Execute`;`CanExecute` 返回 `false` 时自动禁用 |
| `ClickMode` | 点击触发时机:`Release`(默认)或 `Press` |
| `HotKey` | 键盘快捷键 |
| `IsPressed` | 只读,指示按钮当前是否处于按压状态 |
| `Flyout` | 附加弹出层,点击时自动展开 |
| `IsEnabled` | 是否启用 |
**Avalonia Button 提供的基础伪类(IconButton 继承):**
| 伪类 | 说明 |
|---|---|
| `:pointerover` | 鼠标悬浮 |
| `:pressed` | 按钮被按下 |
| `:disabled` | `IsEnabled == false` |
| `:focus` | 获得焦点 |
| `:focus-visible` | 通过键盘获得焦点 |
| `:flyout-open` | 附加的 Flyout 处于打开状态 |
> **注意**:IconButton 并不使用 `Content` / `ContentPresenter`,而是通过专门的 `IconPresenter` 展示图标。`Content` 属性虽然存在(继承自 `ContentControl`),但在 IconButton 的模板中不被使用。
### AtomUI 的扩展设计
AtomUI `IconButton` 在 Avalonia Button 的基础上做了以下扩展:
| 扩展能力 | 实现方式 | 设计动机 |
|---|---|---|
| **图标属性** | `Icon` 属性(`PathIcon?` 类型) | 专门的图标栏位,使用 Ant Design 图标集 |
| **图标颜色** | `IconBrush` 属性 + 主题伪类驱动 | 不同交互状态(正常/悬浮/按下/禁用)自动切换图标颜色 |
| **图标尺寸** | `IconWidth` / `IconHeight` 属性 | 精确控制图标渲染尺寸 |
| **加载动画** | `LoadingAnimation` + `LoadingAnimationDuration` | 支持图标旋转等加载动画效果 |
| **鼠标事件穿透** | `IsPassthroughMouseEvent` 属性 | 嵌入其他控件时不阻断事件冒泡 |
| **过渡动画** | `IMotionAwareControl` + `Background` / `RenderTransform` 过渡 | 平滑的背景色和变换过渡 |
| **Design Token** | `ButtonToken.ScopeProvider` | 复用 Button 的 Token 作用域 |
---
## 功能详解
### 极简模板
IconButton 的模板非常简洁:一个 `Border`(承载背景、边框、圆角)包裹一个 `IconPresenter`。没有 `ContentPresenter`、没有波纹层、没有阴影层——这是有意为之的轻量设计,因为 IconButton 的使用场景通常是在其他控件内部或工具栏中,过重的视觉效果会造成干扰。
### 交互状态颜色系统
IconButton 通过 `IconBrush` 属性在不同状态下切换图标颜色,主题通过伪类选择器自动驱动颜色变化:
| 状态 | IconBrush 来源 | Background |
|---|---|---|
| 正常 | `SharedToken.ColorIcon` | 透明 |
| 悬浮(`:pointerover`) | `SharedToken.ColorIconHover` | 透明 |
| 按下(`:pressed`) | `SharedToken.ColorText` | 透明 |
| 禁用(`:disabled`) | `SharedToken.ColorTextDisabled` | 透明 |
### 鼠标事件穿透
`IsPassthroughMouseEvent` 是 IconButton 特有且至关重要的设计。当 IconButton 嵌入其他控件内部时(如 Input 的清除按钮、Tag 的关闭按钮),需要解决一个常见问题:**嵌套的 Button 会拦截鼠标事件,导致父控件无法正常响应**。
设置 `IsPassthroughMouseEvent = true` 后:
- `OnPointerPressed` / `OnPointerReleased` / `OnPointerMoved` 均将 `e.Handled` 设为 `false`
- 鼠标事件继续冒泡到父控件
- IconButton 自身的 Click 事件仍然正常触发
### 过渡动画
当 `IsMotionEnabled = true` 时,`OnLoaded` 阶段自动配置以下过渡:
- `Background`:`SolidColorBrushTransition`(背景色平滑过渡)
- `RenderTransform`:`TransformOperationsTransition`(缩放/旋转等变换平滑过渡)
`OnUnloaded` 时清除过渡,避免不可见控件消耗资源。
---
## 与 Ant Design 对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
| 纯图标可点击区域 | 各组件内部隐式实现 | ✅ `IconButton` 独立控件 | ✅ 行为对齐,AtomUI 提供了更好的可复用抽象 |
| 悬浮/按下颜色变化 | ✅ | ✅ Token 驱动 | ✅ 完全对齐 |
| 禁用态 | ✅ 灰色调 | ✅ `ColorTextDisabled` | ✅ 完全对齐 |
| 加载动画 | ✅ 旋转 | ✅ `LoadingAnimation` | ✅ 完全对齐 |
---
## 继承关系
```
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.ContentControl
└── Avalonia.Controls.Button (ICommandSource, IClickableControl)
└── AtomUI.Controls.Commons.AbstractIconButton (IMotionAwareControl) ← 设备无关基类
└── AtomUI.Desktop.Controls.IconButton ← 桌面实现
```
**各层级职责划分:**
| 层级 | 提供的能力 |
|---|---|
| `Avalonia.Controls.Button` | 点击交互 → Click 事件、`ICommand` 绑定、`IsPressed` 状态、`HotKey` 快捷键、`Flyout` 弹出 |
| `AbstractIconButton`(基类层) | `Icon` 属性、`IconBrush` 颜色、`IconWidth` / `IconHeight` 尺寸、`LoadingAnimation` 加载动画、`IsPassthroughMouseEvent` 事件穿透、`Background` + `RenderTransform` 过渡动画 |
| `IconButton`(桌面层) | 注册 `ButtonToken.ScopeProvider`,由主题控制视觉表现 |
**实现的共享接口:**
| 接口 | 定义位置 | 作用 |
|---|---|---|
| `IMotionAwareControl` | `AtomUI.Controls` | 支持 `IsMotionEnabled` 过渡动画开关 |
---
## 作为内部构件的广泛使用
IconButton 在 AtomUI 控件体系中被大量复合控件用作内部构件。以下列出所有使用 IconButton 的控件及其具体场景:
| 宿主控件 | 使用场景 | 关键属性 | 模板文件 |
|---|---|---|---|
| **Tag** | 关闭按钮 | `IsPassthroughMouseEvent="True"` | `src/AtomUI.Desktop.Controls/Tag/Themes/TagTheme.axaml` |
| **Alert** | 关闭按钮 | — | `src/AtomUI.Desktop.Controls/Alert/Themes/AlertTheme.axaml` |
| **NotificationCard** | 关闭按钮 | — | `src/AtomUI.Desktop.Controls/Notifications/Themes/NotificationCardTheme.axaml` |
| **TabItem** | 标签关闭按钮 | `IsPassthroughMouseEvent` | `src/AtomUI.Desktop.Controls/TabControl/Themes/BaseTabItemTheme.axaml` |
| **CardTabItem** | 卡片标签关闭按钮 | — | `src/AtomUI.Desktop.Controls/TabControl/Themes/CardTabItemTheme.axaml` |
| **TabControl** | 添加标签按钮 | — | `src/AtomUI.Desktop.Controls/TabControl/Themes/CardTabControlTheme.axaml` |
| **TabScrollViewer** | 滚动指示按钮 | — | `src/AtomUI.Desktop.Controls/TabControl/Themes/BaseTabScrollViewerTheme.axaml` |
| **OverflowMenuItem** | 溢出菜单关闭按钮 | — | `src/AtomUI.Desktop.Controls/TabControl/Themes/BaseOverflowMenuItemTheme.axaml` |
| **CalendarItem** | 前/后月、年导航按钮 | — | `src/AtomUI.Desktop.Controls/DatePicker/CalendarView/` |
| **Drawer** | 关闭按钮 | — | `src/AtomUI.Desktop.Controls/Drawer/Themes/` |
| **OverlayDialog** | 关闭/最大化按钮 | — | `src/AtomUI.Desktop.Controls/Dialog/Themes/` |
| **NumericUpDown** | 增/减步进按钮 | `IsPassthroughMouseEvent="False"` | `src/AtomUI.Desktop.Controls/NumericUpDown/Themes/NumericUpDownTheme.axaml` |
| **Upload** | 删除/预览操作按钮 | — | `src/AtomUI.Desktop.Controls/Upload/Themes/` |
| **TextBox** | 清除按钮 | — | `src/AtomUI.Desktop.Controls/Input/Themes/` |
> 💡 **模式总结**:当 IconButton 嵌入父控件内部且需要父控件正常响应鼠标事件时,设置 `IsPassthroughMouseEvent="True"`;当 IconButton 需要独占点击(如 NumericUpDown 的步进按钮),保持默认 `false`。
---
## 源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 基类 | `src/AtomUI.Controls/Buttons/AbstractIconButton.cs` | 设备无关基类(Icon、颜色、尺寸、事件穿透、动画) |
| 控件类 | `src/AtomUI.Desktop.Controls/Buttons/IconButton.cs` | 桌面端实现(仅注册 Token 作用域) |
| Token 定义 | `src/AtomUI.Desktop.Controls/Buttons/ButtonToken.cs` | 复用 Button 的组件级 Design Token |
| 主题模板 | `src/AtomUI.Desktop.Controls/Buttons/Themes/IconButtonTheme.axaml` | ControlTheme AXAML |
| 主题代码 | `src/AtomUI.Desktop.Controls/Buttons/Themes/IconButtonTheme.cs` | 主题 Code-behind |
---
## 模板结构
IconButton 的 ControlTemplate 采用极简两层结构:
```
Border#Frame (背景、边框、圆角、内间距)
└── IconPresenter#IconPresenter (图标展示器)
└── PathIcon (实际图标渲染)
```
**极简设计理由:**
- **无波纹层**:IconButton 主要作为内部构件使用,波纹效果由外层控件处理(如外层 Button 的 `WaveSpiritDecorator`)
- **无阴影层**:轻量级控件无需阴影增强立体感
- **无 ContentPresenter**:IconButton 仅展示图标,不需要通用内容容器
### 模板部件
| 部件名 | 控件类型 | 说明 |
|---|---|---|
| `Frame` | `Border` | 主框架,承载背景色(默认透明)、边框和圆角 |
| `IconPresenter` | `IconPresenter` | 图标展示器,通过 `IconBrush` 控制颜色,支持 `LoadingAnimation` |