Files
AtomUI/docs/Desktop.Controls/SplitView/SplitView.md
T

250 lines
13 KiB
Markdown
Raw 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.
# SplitView 分割视图
## 概述
分割视图(SplitView)是一个带有可折叠侧边栏(Pane)的布局容器控件。常用于应用的主框架布局——侧边栏承载导航菜单、工具面板等辅助内容,主内容区域展示核心业务页面。用户可以通过切换 `IsPaneOpen` 属性来展开或收起侧边栏。
AtomUI 的 `SplitView` 是对 Avalonia 原生 `Avalonia.Controls.SplitView` 的增强封装。它在继承 Avalonia 所有基础能力的同时,集成了 AtomUI Design Token 系统和平滑过渡动画,使侧边栏的展开/收起过程更加流畅自然。
> 这是 AtomUI 的扩展控件,在 Ant Design 中没有直接对应的组件。其设计灵感来源于 UWP/WinUI 的 NavigationView 模式,同时遵循 AtomUI 的 Design Token 视觉规范。
---
## 设计原理
### 分割视图的设计哲学
SplitView 解决的核心问题是:**在有限的屏幕空间内,同时展示导航/工具内容与主体内容,并提供灵活的空间分配策略**。其设计围绕以下几个关键概念:
**四种显示模式**(DisplayMode),决定了侧边栏与主内容的空间关系:
| 模式 | 设计意图 | 典型用途 |
|---|---|---|
| 📌 **Inline** | 侧边栏与主内容并排,展开时主内容区域被压缩 | 宽屏应用,侧边栏常驻时不遮挡内容 |
| 📐 **CompactInline** | 收起时保留紧凑条(CompactPaneLength),展开时压缩主内容 | 侧导航图标常驻 + 展开显示文字 |
| 🔲 **Overlay** | 侧边栏覆盖在主内容之上,展开时不影响主内容布局 | 窄屏应用,临时弹出导航面板 |
| 📏 **CompactOverlay** | 收起时保留紧凑条,展开时覆盖在主内容之上 | 移动端/窄屏的图标导航 + 弹出详情 |
**四种面板放置位置**(PanePlacement):
| 位置 | 说明 |
|---|---|
| `Left` | 侧边栏在左侧(默认,最常用的导航布局) |
| `Right` | 侧边栏在右侧(如属性面板、详情面板) |
| `Top` | 面板在顶部 |
| `Bottom` | 面板在底部 |
### Avalonia SplitView 基础能力
AtomUI 的 `SplitView` 直接继承自 `Avalonia.Controls.SplitView`。了解 Avalonia SplitView 的基础能力有助于理解 AtomUI 在其之上做了哪些增强。
**Avalonia SplitView 的核心职责:**
Avalonia 的 `SplitView` 是一个将布局区域一分为二的容器控件——一侧为可折叠的 Pane(面板),另一侧为 Content(主内容)。它通过 `SplitViewTemplateSettings` 内部管理面板的列宽/行高等模板参数,支持四种显示模式和四种面板位置。其继承链为:
```
Control → TemplatedControl → ContentControl → SplitView
```
**Avalonia SplitView 提供的基础属性:**
| 属性 | 说明 |
|---|---|
| `Content` | 主内容区域,可以是任意控件 |
| `Pane` | 侧边栏内容,可以是任意控件 |
| `PaneTemplate` / `ContentTemplate` | 面板/主内容的数据模板 |
| `IsPaneOpen` | 面板是否处于展开状态 |
| `DisplayMode` | 显示模式(`Overlay`、`Inline`、`CompactInline`、`CompactOverlay`) |
| `PanePlacement` | 面板放置位置(`Left`、`Right`、`Top`、`Bottom`) |
| `OpenPaneLength` | 面板完全展开时的宽度(水平)或高度(垂直) |
| `CompactPaneLength` | 紧凑模式下面板的宽度/高度 |
| `PaneBackground` | 面板背景画刷 |
| `UseLightDismissOverlayMode` | 是否在 Overlay 模式下启用点击遮罩自动关闭 |
**Avalonia SplitView 提供的基础伪类:**
| 伪类 | 说明 |
|---|---|
| `:open` | 面板处于展开状态 |
| `:closed` | 面板处于收起状态 |
| `:left` | 面板在左侧 |
| `:right` | 面板在右侧 |
| `:top` | 面板在顶部 |
| `:bottom` | 面板在底部 |
| `:overlay` | Overlay 显示模式 |
| `:inline` | Inline 显示模式 |
| `:compactinline` | CompactInline 显示模式 |
| `:compactoverlay` | CompactOverlay 显示模式 |
| `:lightDismiss` | 启用了轻量关闭遮罩 |
**Avalonia SplitView 提供的事件:**
| 事件 | 说明 |
|---|---|
| `PaneClosing` | 面板即将关闭时触发,可取消 |
| `PaneClosed` | 面板关闭完成后触发 |
### AtomUI 的扩展设计
AtomUI `SplitView` 在 Avalonia SplitView 的基础上做了以下增强:
| 扩展能力 | 实现方式 | 设计动机 |
|---|---|---|
| **Design Token 集成** | `SplitViewToken` + `RegisterTokenResourceScope` | 面板尺寸、动画参数等可通过 Token 统一管理,支持主题切换 |
| **平滑过渡动画** | `PaneOpenTransitions` / `PaneCloseTransitions` + 自动配置 | 面板展开/收起时使用缓动动画,而非瞬间切换,提升交互体验 |
| **分离的展开/收起动画时长** | `PaneOpenMotionDuration` / `PaneCloseMotionDuration` | 允许展开和收起使用不同的动画时长,精细控制交互节奏 |
| **自定义缓动曲线** | `PaneMotionEasing` 属性 | 支持自定义贝塞尔缓动曲线,默认使用 `cubic-bezier(0.1, 0.9, 0.2, 1.0)` 实现自然的减速效果 |
| **动画开关** | `IMotionAwareControl` 接口 + `IsMotionEnabled` | 全局或单控件级别禁用动画,适用于性能敏感场景或无障碍需求 |
| **方向感知的过渡** | 根据 `PanePlacement` 自动选择 Width 或 Height 过渡 | 水平面板过渡宽度,垂直面板过渡高度,动画方向始终正确 |
| **Token 驱动的面板背景** | `PaneBackground` 默认绑定 `SharedToken.ColorBgContainer` | 面板背景色跟随主题自动切换(亮色/暗色模式) |
| **Token 驱动的边框颜色** | `HCPaneBorder` 使用 `SharedToken.ColorBorder` | 面板分隔线颜色跟随主题 |
---
## 功能详解
### 显示模式(DisplayMode)
显示模式通过 `DisplayMode` 属性设置,控制侧边栏展开/收起时与主内容区域的空间关系。
| 模式 | 收起时行为 | 展开时行为 |
|---|---|---|
| `Overlay` | 面板完全隐藏(宽度/高度为 0) | 面板覆盖在主内容之上,主内容不移动 |
| `Inline` | 面板完全隐藏(宽度/高度为 0) | 面板与主内容并排,主内容被压缩 |
| `CompactInline` | 面板保留紧凑宽度(`CompactPaneLength`) | 面板展开至 `OpenPaneLength`,主内容被压缩 |
| `CompactOverlay` | 面板保留紧凑宽度(`CompactPaneLength`) | 面板展开并覆盖主内容 |
### 面板放置位置(PanePlacement)
面板位置通过 `PanePlacement` 属性设置。AtomUI 支持四个方向:
- **Left / Right**:面板在水平方向,使用 `Grid.ColumnDefinitions` 布局,过渡 `Width` 属性
- **Top / Bottom**:面板在垂直方向,使用 `Grid.RowDefinitions` 布局,过渡 `Height` 属性
当 `PanePlacement` 改变时,SplitView 会自动重新配置过渡动画(从 Width 过渡切换到 Height 过渡,反之亦然)。
### 过渡动画
SplitView 的面板展开/收起动画是 AtomUI 的核心增强之一。动画行为如下:
1. **OnLoaded 时配置**:控件加载后自动根据 `PanePlacement` 创建对应的 `DoubleTransition`
2. **OnUnloaded 时清除**:控件卸载时清空过渡,避免不可见控件消耗资源
3. **方向自适应**:水平面板(Left/Right)过渡 `Width`,垂直面板(Top/Bottom)过渡 `Height`
4. **展开/收起独立控制**:`PaneOpenTransitions` 和 `PaneCloseTransitions` 分别应用于展开和收起过程,支持不同时长
动画参数通过 Token 或属性控制:
| 参数 | Token 来源 | 默认值 | 说明 |
|---|---|---|---|
| `PaneOpenMotionDuration` | `SplitViewToken.PaneOpenMotionDuration` | 200ms | 展开动画时长 |
| `PaneCloseMotionDuration` | `SplitViewToken.PaneCloseMotionDuration` | 100ms | 收起动画时长 |
| `PaneMotionEasing` | `SplitViewToken.PaneMotionEasing` | `cubic-bezier(0.1, 0.9, 0.2, 1.0)` | 缓动曲线 |
> 缓动曲线 `(0.1, 0.9, 0.2, 1.0)` 实现了「快启动、缓停止」的自然减速效果,符合 Material Design 和 Ant Design 的动效原则。
### 轻量关闭遮罩(Light Dismiss)
在 `Overlay` 和 `CompactOverlay` 模式下,当面板展开时,主内容区域会覆盖一层 `LightDismissLayer`(半透明遮罩),颜色由 `SharedToken.ColorBgMask` 控制。点击遮罩可自动关闭面板(需启用 `UseLightDismissOverlayMode`)。
### 面板边框分隔线
每个面板模板中都包含一条 `HCPaneBorder` 分隔线(`Rectangle`),颜色由 `SharedToken.ColorBorder` 控制:
- Left 面板:分隔线在右边缘
- Right 面板:分隔线在左边缘
- Top 面板:分隔线在底部
- Bottom 面板:分隔线在顶部
---
## 与 Ant Design 规范的对齐程度
SplitView 是 AtomUI 的**扩展控件**,在 Ant Design 5.0 中没有直接对应的组件。但它遵循了 AtomUI Design Token 系统,确保视觉风格与其他 Ant Design 组件保持一致:
| 方面 | 对齐情况 | 说明 |
|---|---|---|
| 颜色系统 | ✅ | 面板背景使用 `ColorBgContainer`,边框使用 `ColorBorder`,遮罩使用 `ColorBgMask`——均来自 SharedToken |
| 动画系统 | ✅ | 缓动曲线和动画时长遵循 Ant Design 动效规范 |
| Token 架构 | ✅ | 使用 `SplitViewToken` 组件级 Token,支持主题定制 |
| 对应 Ant Design 组件 | — | 无直接对应,Ant Design 的 `Layout.Sider` 提供类似功能但 API 不同 |
---
## 继承关系
```
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.ContentControl
└── Avalonia.Controls.SplitView
└── AtomUI.Desktop.Controls.SplitView
└── implements IMotionAwareControl
```
`SplitView` 通过 `using AvaloniaSplitView = Avalonia.Controls.SplitView;` 别名引用 Avalonia 原生 SplitView,避免类名冲突。
**各层级职责划分:**
| 层级 | 提供的能力 |
|---|---|
| `ContentControl` | 主内容容纳、`Content` / `ContentTemplate`、`ContentPresenter` 模板化呈现 |
| `Avalonia.Controls.SplitView` | 面板/主内容双区域布局、四种显示模式、四种面板位置、`IsPaneOpen` 状态管理、`PaneClosing` / `PaneClosed` 事件、`SplitViewTemplateSettings` 模板参数计算 |
| `AtomUI.Desktop.Controls.SplitView` | Design Token 集成、平滑过渡动画(展开/收起独立时长)、自定义缓动曲线、动画开关、Token 驱动的背景/边框颜色 |
**实现的共享接口:**
| 接口 | 定义位置 | 作用 |
|---|---|---|
| `IMotionAwareControl` | `AtomUI.Core` | 支持 `IsMotionEnabled` 动画开关,全局或单控件级别禁用过渡动画 |
---
## 源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 控件类 | `src/AtomUI.Desktop.Controls/SplitView/SplitView.cs` | 桌面端 SplitView 实现 |
| Token 定义 | `src/AtomUI.Desktop.Controls/SplitView/SplitViewToken.cs` | 组件级 Design Token |
| 主题模板 | `src/AtomUI.Desktop.Controls/SplitView/Themes/SplitViewTheme.axaml` | ControlTheme AXAML(包含四种面板方向的模板定义) |
> 注意:SplitView 没有对应的 `AbstractSplitView` 基类在 `AtomUI.Controls` 中——它直接继承 Avalonia 的 `SplitView`,因为分割视图的行为在各平台上基本一致,不需要抽象基类层。
---
## 模板结构
SplitView 为四种面板位置分别定义了独立的 ControlTemplate。以 Left 面板为例:
```
Grid#Container (Background = TemplateBinding Background)
├── ColumnDefinition (PaneColumnGridLength) ← 面板列(宽度由 TemplateSettings 计算)
├── ColumnDefinition (*) ← 主内容列(填充剩余空间)
│
├── Panel#PART_PaneRoot (Column=0) ← 面板容器(ClipToBounds, ZIndex=100)
│ ├── ContentPresenter#PART_PanePresenter ← 面板内容展示器
│ └── Rectangle#HCPaneBorder ← 面板右边缘分隔线(1px, ColorBorder)
│
└── Panel#ContentRoot (Column=1) ← 主内容容器
├── ContentPresenter#PART_ContentPresenter ← 主内容展示器
└── Rectangle#LightDismissLayer ← 遮罩层(Overlay 模式展开时可见)
```
**四种方向的布局差异:**
| 面板位置 | 布局方式 | 面板对齐 | 分隔线位置 |
|---|---|---|---|
| Left | `Grid.ColumnDefinitions` | `HorizontalAlignment="Left"` | 右边缘(`HorizontalAlignment="Right"`) |
| Right | `Grid.ColumnDefinitions` | `HorizontalAlignment="Right"` | 左边缘(`HorizontalAlignment="Left"`) |
| Top | `Grid.RowDefinitions` | `VerticalAlignment="Top"` | 底部(`VerticalAlignment="Bottom"`) |
| Bottom | `Grid.RowDefinitions` | `VerticalAlignment="Bottom"` | 顶部(`VerticalAlignment="Top"`) |
**显示模式对布局的影响:**
不同的 `DisplayMode` 通过伪类选择器调整面板的 `Grid.Column` / `Grid.ColumnSpan`(或 `Grid.Row` / `Grid.RowSpan`)和尺寸绑定,实现不同的空间分配策略。
**过渡动画的应用位置:**
动画通过伪类组合选择器精确控制:
- `:left:open` / `:right:open` → 使用 `PaneOpenTransitions`,目标 Width = `OpenPaneLength`
- `:left:closed` / `:right:closed` → 使用 `PaneCloseTransitions`,目标 Width = `ClosedPaneWidth`
- `:top:open` / `:bottom:open` → 使用 `PaneOpenTransitions`,目标 Height = `OpenPaneLength`
- `:top:closed` / `:bottom:closed` → 使用 `PaneCloseTransitions`,目标 Height = `ClosedPaneHeight`