mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-20 23:58:05 +08:00
202 lines
11 KiB
Markdown
202 lines
11 KiB
Markdown
# 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` |
|