mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-21 16:36:27 +08:00
272 lines
16 KiB
Markdown
272 lines
16 KiB
Markdown
# NavMenu 导航菜单
|
||
|
||
## 概述
|
||
|
||
导航菜单(NavMenu)是网站和应用中最常见的导航交互控件之一,用于为用户提供清晰的页面导航路径。它支持**水平**(Horizontal)、**垂直**(Vertical)和**内嵌**(Inline)三种展示模式,并可在亮色和暗色主题之间切换。
|
||
|
||
AtomUI 的 `NavMenu` 控件对标 [Ant Design 5.0 Menu(导航菜单)](https://ant.design/components/menu-cn) 的设计规范,在 .NET / Avalonia 平台上提供一致的视觉体验和交互行为。
|
||
|
||
---
|
||
|
||
## 设计原理
|
||
|
||
### Ant Design 的导航菜单设计哲学
|
||
|
||
Ant Design 导航菜单的核心定位是:**为页面和功能提供导航的菜单列表,帮助用户在不同层级的内容之间快速跳转**。菜单支持多级层级结构,用户可以展开/收起子菜单来浏览不同层级的导航项。
|
||
|
||
**三种菜单模式**(按场景区分):
|
||
|
||
| 模式 | 设计意图 | 典型场景 |
|
||
|---|---|---|
|
||
| 🔹 **水平模式(Horizontal)** | 水平排列的一级菜单项,子菜单通过弹出层展示。适合顶部导航栏 | 网站/应用的顶部全局导航 |
|
||
| 🔸 **垂直模式(Vertical)** | 垂直排列的菜单项,子菜单通过弹出层(Popup)在侧边展示 | 侧边栏导航(弹出式子菜单) |
|
||
| 🔻 **内嵌模式(Inline)** | 垂直排列的菜单项,子菜单在当前面板内展开/折叠,具有展开/收起动画 | 侧边栏导航(折叠式子菜单) |
|
||
|
||
**关键交互特性**:
|
||
|
||
| 特性 | 说明 |
|
||
|---|---|
|
||
| **选中高亮** | 当前选中的菜单项高亮显示,祖先路径上的父级菜单项也会标记为「选中路径」 |
|
||
| **手风琴模式** | 同级菜单项中只能有一个展开,展开新子菜单时自动关闭其他已展开的兄弟菜单 |
|
||
| **默认展开/选中** | 支持通过路径指定初始展开的子菜单和默认选中的菜单项 |
|
||
| **暗色主题** | 支持独立的暗色主题样式,常用于深色背景的侧边导航栏 |
|
||
| **图标支持** | 菜单项可配置图标,提升可识别性 |
|
||
| **禁用状态** | 单个菜单项可设为禁用,不响应交互 |
|
||
|
||
### 与 Avalonia 的关系
|
||
|
||
AtomUI 的 `NavMenu` **不继承自 Avalonia 内置的 `Menu` 控件**,而是直接继承自 `ItemsControl`。这是因为 Ant Design 的导航菜单与传统的应用程序菜单(File/Edit/View 等)有本质区别——它是一个页面级的导航组件,需要支持选中状态跟踪、路径高亮、内嵌展开动画等 Avalonia `Menu` 不具备的功能。
|
||
|
||
`NavMenu` 使用自定义的 `NavMenuItem`(继承自 `HeaderedSelectingItemsControl`)作为菜单项容器,实现了完整的自定义交互逻辑。
|
||
|
||
### AtomUI NavMenu 的核心设计
|
||
|
||
| 设计要素 | 实现方式 | 设计动机 |
|
||
|---|---|---|
|
||
| **三种菜单模式** | `NavMenuMode` 枚举 + 伪类驱动 + 交互处理器切换 | 不同模式对应不同的布局策略和子菜单展示方式 |
|
||
| **交互处理器策略** | `INavMenuInteractionHandler` 接口 + `DefaultNavMenuInteractionHandler` / `InlineNavMenuInteractionHandler` | 将交互逻辑从控件中解耦,不同模式使用不同的处理器 |
|
||
| **树形数据模型** | `INavMenuNode` / `NavMenuNode` + `ITreeNode<T>` | 支持层级数据结构,每个节点可包含子节点 |
|
||
| **选中路径追踪** | `IsInSelectedPath` 属性 + `CollectSelectPathItems` | 从选中项向上追溯到根节点,标记整条路径上的祖先菜单项 |
|
||
| **默认展开/选中** | `DefaultOpenPaths` / `DefaultSelectedPath` + `TreeNodePath` | 通过路径字符串指定初始状态 |
|
||
| **手风琴模式** | `IsAccordionMode` 属性 + `NotifySubmenuOpened` 互斥逻辑 | 同级只允许一个子菜单展开 |
|
||
| **亮色/暗色主题** | `IsDarkStyle` 属性 + `:dark` / `:light` 伪类 | 通过 Token 系统提供完整的暗色主题 |
|
||
| **展开/折叠动画** | Inline 模式下使用 `SlideUpInMotion` / `SlideUpOutMotion` | 平滑的子菜单展开/收起效果 |
|
||
| **Design Token** | `NavMenuToken` + 全局 `SharedToken` | 所有视觉属性从 Token 派生,支持主题定制 |
|
||
|
||
---
|
||
|
||
## 功能详解
|
||
|
||
### 菜单模式(NavMenuMode)
|
||
|
||
#### 水平模式(Horizontal)
|
||
|
||
菜单项水平排列成一行,通常用于页面顶部导航。子菜单通过 Popup 弹出层在菜单项下方展示。水平模式下选中项有底部指示条效果。
|
||
|
||
#### 垂直模式(Vertical)
|
||
|
||
菜单项垂直排列,子菜单通过 Popup 弹出层在菜单项右侧展示。鼠标悬浮时延迟展开子菜单,移出后延迟关闭。
|
||
|
||
#### 内嵌模式(Inline)
|
||
|
||
菜单项垂直排列,子菜单在当前面板内通过折叠/展开方式显示。点击有子菜单的菜单项会触发展开/收起动画(`SlideUpInMotion` / `SlideUpOutMotion`)。
|
||
|
||
**模式与交互处理器的对应关系:**
|
||
|
||
| 模式 | 交互处理器 | 子菜单展示方式 |
|
||
|---|---|---|
|
||
| Horizontal | `DefaultNavMenuInteractionHandler` | Popup 弹出层 |
|
||
| Vertical | `DefaultNavMenuInteractionHandler` | Popup 弹出层 |
|
||
| Inline | `InlineNavMenuInteractionHandler` | 就地展开/折叠 |
|
||
|
||
### 选中路径追踪
|
||
|
||
当用户选中一个叶子菜单项时,NavMenu 会:
|
||
1. 清除之前选中项及其祖先路径上的 `IsInSelectedPath` 标记
|
||
2. 为新选中项的所有祖先菜单项设置 `IsInSelectedPath = true`
|
||
3. 触发 `NavMenuNodeSelected` 事件,更新 `SelectedItem` 属性
|
||
|
||
这使得主题可以为整条选中路径上的菜单项应用高亮样式。
|
||
|
||
### 手风琴模式(Accordion)
|
||
|
||
当 `IsAccordionMode = true` 时,同一级别中只能有一个子菜单处于展开状态。当一个子菜单被展开时,其兄弟子菜单会自动关闭。
|
||
|
||
### 默认展开与默认选中
|
||
|
||
- **`DefaultOpenPaths`**:通过 `TreeNodePath` 列表指定初始展开的路径。路径格式为 `/Key1/Key2/Key3`,由各节点的 `ItemKey` 拼接而成。
|
||
- **`DefaultSelectedPath`**:指定初始选中的菜单项路径。
|
||
- **`SelectedItem`**:直接设置选中的 `INavMenuNode` 对象,优先级高于 `DefaultSelectedPath`。
|
||
|
||
### 亮色/暗色主题
|
||
|
||
通过 `IsDarkStyle` 属性切换菜单的视觉主题。暗色模式下使用一套独立的 Token(如 `DarkMenuBg`、`DarkItemColor`、`DarkItemSelectedBg` 等),提供深色背景下的完整样式支持。
|
||
|
||
---
|
||
|
||
## 与 Ant Design 规范的对齐程度
|
||
|
||
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|
||
|---|---|---|---|
|
||
| 三种模式 `mode` | ✅ `vertical / horizontal / inline` | ✅ `NavMenuMode` 枚举 | ✅ 完全对齐 |
|
||
| 暗色主题 `theme` | ✅ `light / dark` | ✅ `IsDarkStyle` 属性 | ✅ 完全对齐 |
|
||
| 选中项 `selectedKeys` | ✅ 数组 | ✅ `SelectedItem` 单选 | ⚠️ 仅支持单选 |
|
||
| 默认展开 `defaultOpenKeys` | ✅ 数组 | ✅ `DefaultOpenPaths` 路径列表 | ✅ 对齐(路径格式不同) |
|
||
| 默认选中 `defaultSelectedKeys` | ✅ 数组 | ✅ `DefaultSelectedPath` 单路径 | ⚠️ 仅支持单选 |
|
||
| 手风琴模式 | ✅ `openKeys` 受控 | ✅ `IsAccordionMode` 属性 | ✅ 对齐 |
|
||
| 图标支持 `icon` | ✅ ReactNode | ✅ `PathIcon` | ✅ 对齐 |
|
||
| 禁用项 `disabled` | ✅ 布尔 | ✅ `IsEnabled` | ✅ 完全对齐 |
|
||
| 菜单项分组 `ItemGroup` | ✅ 支持 | ❌ 暂未支持 | ⚠️ 待实现 |
|
||
| 菜单分割线 `Divider` | ✅ 支持 | ❌ 暂未支持 | ⚠️ 待实现 |
|
||
| 折叠收起 `inlineCollapsed` | ✅ 支持 | ❌ 暂未支持 | ⚠️ 待实现 |
|
||
| 点击事件 `onClick` | ✅ 回调 | ✅ `NavMenuItemClick` 事件 | ✅ 完全对齐 |
|
||
| 选中事件 `onSelect` | ✅ 回调 | ✅ `NavMenuNodeSelected` 事件 | ✅ 完全对齐 |
|
||
| `items` 配置式 | ✅ JSON 数组 | ✅ `NavMenuNode` 数据模型 | ✅ 对齐(形式不同) |
|
||
|
||
---
|
||
|
||
## 继承关系
|
||
|
||
### NavMenu
|
||
|
||
```
|
||
Avalonia.Controls.Primitives.TemplatedControl
|
||
└── Avalonia.Controls.ItemsControl
|
||
└── AtomUI.Desktop.Controls.NavMenu
|
||
├── implements IFocusScope
|
||
├── implements INavMenu
|
||
├── implements IMotionAwareControl
|
||
└── implements IMenuChildSelectable (internal)
|
||
```
|
||
|
||
### NavMenuItem
|
||
|
||
```
|
||
Avalonia.Controls.Primitives.TemplatedControl
|
||
└── Avalonia.Controls.ItemsControl
|
||
└── Avalonia.Controls.Primitives.SelectingItemsControl
|
||
└── Avalonia.Controls.Primitives.HeaderedSelectingItemsControl
|
||
└── AtomUI.Desktop.Controls.NavMenuItem (internal)
|
||
├── implements INavMenuItem
|
||
├── implements ISelectable
|
||
├── implements ICommandSource
|
||
├── implements IClickableControl
|
||
├── implements ICustomHitTest
|
||
└── implements IMenuChildSelectable (internal)
|
||
```
|
||
|
||
### NavMenuNode(数据模型)
|
||
|
||
```
|
||
Avalonia.AvaloniaObject
|
||
└── AtomUI.Desktop.Controls.NavMenuNode
|
||
└── implements INavMenuNode
|
||
└── extends ITreeNode<INavMenuNode>
|
||
```
|
||
|
||
**各层级职责划分:**
|
||
|
||
| 层级 | 提供的能力 |
|
||
|---|---|
|
||
| `ItemsControl` | 子项集合管理、容器化、`ItemsSource` 绑定、`ItemTemplate` 模板 |
|
||
| `NavMenu` | 三种模式切换、交互处理器调度、选中路径追踪、默认展开/选中、手风琴模式、亮色/暗色主题、Design Token 集成 |
|
||
| `NavMenuItem` | 单个菜单项行为:子菜单展开/关闭、命令绑定、选中状态、Popup 管理、内嵌展开动画、层级计算 |
|
||
| `NavMenuNode` | 数据模型:Header、Icon、ItemKey、子节点集合、父节点引用 |
|
||
|
||
---
|
||
|
||
## 核心类型
|
||
|
||
### 接口体系
|
||
|
||
| 接口 | 定义位置 | 职责 |
|
||
|---|---|---|
|
||
| `INavMenu` | `AtomUI.Desktop.Controls` | NavMenu 根控件接口,提供 SelectedItem、DefaultOpenPaths、Close 等能力 |
|
||
| `INavMenuElement` | `AtomUI.Desktop.Controls` | 菜单元素基接口,提供 SubItems 子项枚举 |
|
||
| `INavMenuItem` | `AtomUI.Desktop.Controls` | 菜单项接口,定义 HasSubMenu、IsSubMenuOpen、Open/Close 等行为 |
|
||
| `INavMenuNode` | `AtomUI.Desktop.Controls` | 菜单节点数据接口,继承自 `ITreeNode<INavMenuNode>` |
|
||
| `INavMenuInteractionHandler` | `AtomUI.Desktop.Controls` (internal) | 交互处理器接口,策略模式 |
|
||
| `IMenuChildSelectable` | `AtomUI.Desktop.Controls` (internal) | 子项选中管理接口 |
|
||
|
||
### 辅助类型
|
||
|
||
| 类型 | 说明 |
|
||
|---|---|
|
||
| `NavMenuNode` | `INavMenuNode` 的默认实现,可直接在 AXAML 中声明使用 |
|
||
| `NavNodeKey` | 菜单节点键值结构体,支持字符串比较 |
|
||
| `TreeNodePath` | 树节点路径,格式为 `/Key1/Key2`,用于指定默认展开/选中路径 |
|
||
| `NavMenuItemClickEventArgs` | 菜单项点击事件参数 |
|
||
| `NavMenuNodeSelectedEventArgs` | 菜单节点选中事件参数 |
|
||
| `BaseNavMenuItemHeader` | 菜单项头部基类,承载 Header、Icon、动画过渡等通用逻辑 |
|
||
| `HorizontalNavMenuItemHeader` | 水平模式专用的菜单项头部 |
|
||
| `VerticalNavMenuItemHeader` | 垂直模式专用的菜单项头部 |
|
||
| `InlineNavMenuItemHeader` | 内嵌模式专用的菜单项头部 |
|
||
|
||
---
|
||
|
||
## 源码位置
|
||
|
||
| 层级 | 文件路径 | 说明 |
|
||
|---|---|---|
|
||
| 控件类 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenu.cs` | NavMenu 主控件 |
|
||
| 菜单项 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuItem.cs` | NavMenuItem 菜单项容器(internal) |
|
||
| 数据模型 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuNode.cs` | NavMenuNode 数据模型 + INavMenuNode 接口 |
|
||
| Token 定义 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuToken.cs` | 组件级 Design Token |
|
||
| 伪类常量 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuPseudoClass.cs` | 伪类定义 |
|
||
| 模式枚举 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuMode.cs` | NavMenuMode 枚举 |
|
||
| 事件参数 | `src/AtomUI.Desktop.Controls/NavMenu/NavMenuEventArgs.cs` | 事件参数类 |
|
||
| 接口定义 | `src/AtomUI.Desktop.Controls/NavMenu/INavMenu.cs` | INavMenu 接口 |
|
||
| 接口定义 | `src/AtomUI.Desktop.Controls/NavMenu/INavMenuElement.cs` | INavMenuElement 接口 |
|
||
| 接口定义 | `src/AtomUI.Desktop.Controls/NavMenu/INavMenuItem.cs` | INavMenuItem 接口 |
|
||
| 键结构体 | `src/AtomUI.Desktop.Controls/NavMenu/NavNodeKey.cs` | NavNodeKey 结构体 |
|
||
| 交互处理器 | `src/AtomUI.Desktop.Controls/NavMenu/DefaultNavMenuInteractionHandler.cs` | 默认(Vertical/Horizontal)交互处理器 |
|
||
| 交互处理器 | `src/AtomUI.Desktop.Controls/NavMenu/InlineNavMenuInteractionHandler.cs` | 内嵌模式交互处理器 |
|
||
| 头部控件 | `src/AtomUI.Desktop.Controls/NavMenu/Header/BaseNavMenuItemHeader.cs` | 菜单项头部基类 |
|
||
| 头部控件 | `src/AtomUI.Desktop.Controls/NavMenu/Header/HorizontalNavMenuItemHeader.cs` | 水平模式头部 |
|
||
| 头部控件 | `src/AtomUI.Desktop.Controls/NavMenu/Header/VerticalNavMenuItemHeader.cs` | 垂直模式头部 |
|
||
| 头部控件 | `src/AtomUI.Desktop.Controls/NavMenu/Header/InlineNavMenuItemHeader.cs` | 内嵌模式头部 |
|
||
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/NavMenuTheme.axaml` | NavMenu ControlTheme |
|
||
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/NavMenuItemTheme.axaml` | NavMenuItem ControlTheme |
|
||
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/BaseNavMenuItemHeaderTheme.axaml` | 基础头部主题 |
|
||
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/HorizontalNavMenuItemHeaderTheme.axaml` | 水平头部主题 |
|
||
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/VerticalNavMenuItemHeaderTheme.axaml` | 垂直头部主题 |
|
||
| 主题模板 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/InlineNavMenuItemHeaderTheme.axaml` | 内嵌头部主题 |
|
||
| 模板常量 | `src/AtomUI.Desktop.Controls/NavMenu/Themes/NavMenuThemeConstants.cs` | 模板部件名称常量 |
|
||
| 工具类 | `src/AtomUI.Desktop.Controls/NavMenu/Utils/MarginMultiplierConverter.cs` | 缩进边距倍数转换器 |
|
||
| 工具类 | `src/AtomUI.Desktop.Controls/NavMenu/Utils/NavMenuIsSubMenuOpenConverter.cs` | 子菜单展开状态转换器 |
|
||
| Gallery 示例 | `controlgallery/AtomUIGallery/ShowCases/Views/Navigation/MenuShowCase.axaml` | 使用范例 |
|
||
| Gallery 代码 | `controlgallery/AtomUIGallery/ShowCases/Views/Navigation/MenuShowCase.axaml.cs` | 示例 code-behind |
|
||
|
||
---
|
||
|
||
## 模板结构
|
||
|
||
### NavMenu 模板
|
||
|
||
NavMenu 自身模板比较简单,主要是一个带 `ItemsPresenter` 的容器:
|
||
|
||
- **水平模式**:`ItemsPanel` 使用水平布局的 `StackPanel`,底部有一条指示线
|
||
- **垂直/内嵌模式**:`ItemsPanel` 使用垂直布局的 `StackPanel`
|
||
|
||
### NavMenuItem 模板
|
||
|
||
NavMenuItem 的模板根据模式不同有较大差异:
|
||
|
||
- **水平/垂直模式**:使用 `Popup` 弹出子菜单
|
||
- **内嵌模式**:使用 `BaseMotionActor`(`PART_ChildItemsLayoutTransform`)包裹子菜单 `ItemsPresenter`,通过 `SlideUpInMotion` / `SlideUpOutMotion` 实现展开/收起动画
|
||
|
||
每种模式都有对应的 `NavMenuItemHeader` 控件作为菜单项头部区域。
|
||
|
||
### 模板部件常量
|
||
|
||
| 常量名 | 值 | 说明 |
|
||
|---|---|---|
|
||
| `NavMenuItemThemeConstants.HeaderPart` | `"PART_Header"` | 菜单项头部区域 |
|
||
| `NavMenuItemThemeConstants.ItemsPresenterPart` | `"PART_ItemsPresenter"` | 子项展示器 |
|
||
| `NavMenuItemThemeConstants.PopupFramePart` | `"PART_PopupFrame"` | Popup 框架 |
|
||
| `NavMenuItemThemeConstants.PopupPart` | `"PART_Popup"` | Popup 弹出层 |
|
||
| `InlineNavMenuItemThemeConstants.ChildItemsLayoutTransformPart` | `"PART_ChildItemsLayoutTransform"` | 内嵌模式子项动画容器 |
|
||
| `NavMenuThemeConstants.ItemsPresenterPart` | `"PART_ItemsPresenter"` | NavMenu 子项展示器 |
|
||
| `NavMenuThemeConstants.HorizontalLinePart` | `"PART_HorizontalLine"` | 水平模式底部指示线 |
|
||
| `TopLevelHorizontalNavMenuItemThemeConstants.ActiveIndicatorPart` | `"PART_ActiveIndicator"` | 水平顶层菜单项的活跃指示器 |
|