13 KiB
Splitter 分割面板
概述
分割面板(Splitter)允许用户通过拖拽手柄来动态调整两个或多个面板之间的大小比例。它广泛应用于 IDE 布局、仪表盘、文件管理器等需要灵活空间分配的场景。Splitter 支持水平与垂直方向分割、多面板嵌套、面板折叠/展开、最小/最大尺寸约束、百分比与像素混合指定尺寸等特性。
AtomUI 的 Splitter 控件对齐了 Ant Design 5.0 Splitter 的设计规范,在 .NET / Avalonia 平台上提供与 Ant Design React 版一致的视觉体验和交互行为。
设计原理
Ant Design 的 Splitter 设计哲学
Ant Design 5.0 的 Splitter 组件设计遵循以下原则:
- 直觉化拖拽:用户通过拖拽分割手柄来调整面板大小,提供即时的视觉反馈。
- 灵活的尺寸约束:支持百分比(
%)和像素(px)两种尺寸单位,可混合使用,并可设置最小/最大尺寸约束。 - 可折叠面板:面板可配置为可折叠(collapsible),用户可通过点击手柄上的箭头图标快速折叠/展开面板。
- 嵌套组合:多个 Splitter 可以嵌套使用,形成复杂的多区域布局。
- 延迟渲染模式:
IsLazy模式下,面板尺寸仅在拖拽释放时更新,适用于内容渲染开销较大的场景。
AtomUI Splitter 的架构设计
AtomUI 的 Splitter 由三个核心部分组成:
| 组件 | 职责 |
|---|---|
Splitter |
公共 API 入口控件,继承自 TemplatedControl。定义公共属性和附加属性,承载子面板集合,转发事件 |
SplitterPanel |
内部布局面板(internal),继承自 Panel。负责全部布局计算、拖拽交互处理、折叠逻辑 |
SplitterHandle |
内部拖拽手柄(internal),继承自 Thumb。负责拖拽交互和折叠按钮的呈现与响应 |
设计分离理由:
Splitter作为公共 API 面,暴露给用户的属性和事件均经过精心设计,保持最小化和清晰性。SplitterPanel封装了复杂的布局算法(包括多面板尺寸计算、归一化、折叠状态管理),作为internal类隐藏实现细节。SplitterHandle封装了拖拽 Thumb 交互、折叠按钮图标管理、悬浮侧检测等,作为internal类对用户不可见。
尺寸系统
Splitter 使用 Dimension 结构体(定义在 AtomUI.Core)来表达面板尺寸,支持两种单位:
| 单位类型 | AXAML 语法 | 含义 |
|---|---|---|
| 百分比 | "30%" |
占可用空间的百分比(0~100) |
| 像素 | "200" 或 "200px" |
固定像素值 |
尺寸通过附加属性设置在每个子面板上:
Splitter.Size:当前面板尺寸(双向绑定),拖拽过程中会实时更新。Splitter.DefaultSize:面板初始尺寸,仅在首次布局时生效。Splitter.MinSize:面板最小尺寸约束。Splitter.MaxSize:面板最大尺寸约束。
未指定尺寸的面板将平分剩余空间。
折叠系统
面板的折叠行为通过 Splitter.Collapsible 附加属性配置,使用 SplitterPanelCollapsible 类型:
| 属性 | 类型 | 说明 |
|---|---|---|
IsEnabled |
bool |
是否允许折叠 |
ShowCollapsibleIcon |
SplitterCollapsibleIconDisplayMode |
折叠图标显示模式 |
SplitterCollapsibleIconDisplayMode 枚举:
| 值 | 说明 |
|---|---|
Hover(默认 / Auto) |
鼠标悬浮在手柄上时显示折叠图标 |
Always(True) |
始终显示折叠图标 |
Hidden(False) |
隐藏折叠图标 |
折叠后的面板尺寸变为 0,展开时恢复到折叠前的尺寸。
功能详解
基本分割
最简单的使用方式是将两个或多个子控件放入 Splitter 中,系统自动在相邻面板之间生成拖拽手柄:
<atom:Splitter Orientation="Vertical" Height="220">
<Border atom:Splitter.Size="30%">
<TextBlock>First</TextBlock>
</Border>
<Border atom:Splitter.DefaultSize="100" atom:Splitter.MinSize="60">
<TextBlock>Second</TextBlock>
</Border>
</atom:Splitter>
多面板分割
Splitter 支持任意数量的子面板,系统会在每对相邻面板之间自动插入手柄:
<atom:Splitter Orientation="Vertical" Height="240">
<Border atom:Splitter.Size="20%"><TextBlock>A</TextBlock></Border>
<Border atom:Splitter.Size="20%"><TextBlock>B</TextBlock></Border>
<Border><TextBlock>C</TextBlock></Border>
<Border><TextBlock>D</TextBlock></Border>
</atom:Splitter>
嵌套分割
水平与垂直 Splitter 可以嵌套,构建复杂的多区域布局:
<atom:Splitter Orientation="Vertical" Height="260">
<Border atom:Splitter.Size="40%">
<TextBlock>Left</TextBlock>
</Border>
<atom:Splitter Orientation="Horizontal">
<Border><TextBlock>Top</TextBlock></Border>
<Border><TextBlock>Bottom</TextBlock></Border>
</atom:Splitter>
</atom:Splitter>
禁用拖拽
通过 Splitter.IsResizable="False" 禁止特定面板参与拖拽:
<atom:Splitter Orientation="Vertical" Height="220">
<Border atom:Splitter.Size="35%"><TextBlock>Resizable</TextBlock></Border>
<Border atom:Splitter.DefaultSize="120" atom:Splitter.IsResizable="False">
<TextBlock>Not Resizable</TextBlock>
</Border>
<Border atom:Splitter.DefaultSize="120"><TextBlock>Resizable</TextBlock></Border>
</atom:Splitter>
注意:如果手柄两侧的任一面板设置了
IsResizable="False",该手柄将不可拖拽,光标也不会变为调整大小光标。
面板折叠
通过 Splitter.Collapsible 配置面板可折叠:
<atom:Splitter Orientation="Vertical" Height="220">
<Border atom:Splitter.DefaultSize="33%" atom:Splitter.Collapsible="Always">
<TextBlock>First</TextBlock>
</Border>
<Border atom:Splitter.DefaultSize="34%" atom:Splitter.Collapsible="Hover">
<TextBlock>Second</TextBlock>
</Border>
<Border atom:Splitter.Collapsible="True">
<TextBlock>Third</TextBlock>
</Border>
</atom:Splitter>
也可以通过代码动态设置:
Splitter.SetCollapsible(panel, new SplitterPanelCollapsible
{
IsEnabled = true,
ShowCollapsibleIcon = SplitterCollapsibleIconDisplayMode.Always
});
编程式折叠/展开
通过 Splitter.IsCollapsed 附加属性可以编程式控制面板折叠状态:
<Border x:Name="LeftPanel" atom:Splitter.IsCollapsed="{Binding IsLeftCollapsed}">
...
</Border>
延迟渲染模式
当 IsLazy="True" 时,拖拽过程中仅移动手柄的视觉位置(通过 RenderTransform),面板实际尺寸在释放鼠标后才更新。适用于面板内容渲染开销较大的场景:
<atom:Splitter Orientation="Vertical" Height="140" IsLazy="True">
<Border atom:Splitter.Size="50%"><TextBlock>First</TextBlock></Border>
<Border><TextBlock>Second</TextBlock></Border>
</atom:Splitter>
自定义折叠图标
通过 CollapsePreviousIcon 和 CollapseNextIcon 属性可自定义手柄上的折叠箭头图标:
<atom:Splitter CollapsePreviousIcon="{antdicons:AntDesignIconProvider Kind=LeftOutlined}"
CollapseNextIcon="{antdicons:AntDesignIconProvider Kind=RightOutlined}">
...
</atom:Splitter>
默认图标根据方向自动选择:
- 垂直分割:
LeftOutlined/RightOutlined - 水平分割:
UpOutlined/DownOutlined
拖拽事件
Splitter 提供三个拖拽事件用于监听尺寸变化:
| 事件 | 触发时机 |
|---|---|
ResizeStarted |
拖拽开始 |
ResizeDelta |
拖拽过程中(持续触发) |
ResizeCompleted |
拖拽结束 |
事件参数 SplitterResizeEventArgs 包含:
HandleIndex:被拖拽的手柄索引Sizes:所有面板的当前尺寸列表(IReadOnlyList<double>)
与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
| 水平/垂直方向 | ✅ layout: horizontal / vertical |
✅ Orientation 属性 |
✅ 完全对齐 |
面板尺寸 size |
✅ 百分比 / 像素 / 自适应 | ✅ Splitter.Size 附加属性(Dimension 类型) |
✅ 完全对齐 |
默认尺寸 defaultSize |
✅ 面板初始尺寸 | ✅ Splitter.DefaultSize 附加属性 |
✅ 完全对齐 |
最小/最大约束 min / max |
✅ 面板约束 | ✅ Splitter.MinSize / Splitter.MaxSize |
✅ 完全对齐 |
禁止拖拽 resizable |
✅ 布尔属性 | ✅ Splitter.IsResizable 附加属性 |
✅ 完全对齐 |
面板折叠 collapsible |
✅ 折叠配置 | ✅ Splitter.Collapsible 附加属性 |
✅ 完全对齐 |
| 折叠图标显示模式 | ✅ showCollapsibleIcon |
✅ SplitterCollapsibleIconDisplayMode |
✅ 完全对齐 |
延迟渲染 lazy |
✅ 布尔属性 | ✅ IsLazy 属性 |
✅ 完全对齐 |
拖拽事件 onResize* |
✅ 回调函数 | ✅ ResizeStarted / ResizeDelta / ResizeCompleted |
✅ 完全对齐 |
| 嵌套分割 | ✅ 支持 | ✅ Splitter 可嵌套 | ✅ 完全对齐 |
| 多面板 | ✅ 支持 | ✅ 任意数量子面板 | ✅ 完全对齐 |
| 自定义折叠图标 | ✅ collapsible.start / .end |
✅ CollapsePreviousIcon / CollapseNextIcon |
✅ 完全对齐 |
继承关系
Avalonia.Controls.Primitives.TemplatedControl
└── AtomUI.Desktop.Controls.Splitter
注意:Splitter 控件目前仅存在于桌面平台层(AtomUI.Desktop.Controls),没有设备无关的抽象基类(AtomUI.Controls 中无 AbstractSplitter)。这是因为 Splitter 的交互模式(鼠标拖拽)在移动端有本质不同,未来移动端可能采用完全不同的实现方案。
内部组件关系
Splitter (public, TemplatedControl)
├── [template] SplitterPanel (internal, Panel)
│ ├── 子面板 (用户提供的 Control)
│ └── SplitterHandle[] (internal, Thumb) — 自动生成
│ ├── [template] HandleLine (Border)
│ ├── [template] HandleGrip (Border)
│ ├── [template] CollapsePrevButton (IconButton)
│ └── [template] CollapseNextButton (IconButton)
源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 控件类 | src/AtomUI.Desktop.Controls/Splitter/Splitter.cs |
公共 API 入口控件 |
| 内部布局面板 | src/AtomUI.Desktop.Controls/Splitter/SplitterPanel.cs |
布局计算与拖拽处理 |
| 内部拖拽手柄 | src/AtomUI.Desktop.Controls/Splitter/SplitterHandle.cs |
手柄交互与折叠按钮 |
| 折叠配置 | src/AtomUI.Desktop.Controls/Splitter/SplitterPanelCollapsible.cs |
SplitterPanelCollapsible 类与枚举 |
| 事件参数 | src/AtomUI.Desktop.Controls/Splitter/SplitterResizeEventArgs.cs |
SplitterResizeEventArgs 类 |
| Token 定义 | src/AtomUI.Desktop.Controls/Splitter/SplitterToken.cs |
组件级 Design Token |
| 主题模板 | src/AtomUI.Desktop.Controls/Splitter/Themes/SplitterTheme.axaml |
Splitter ControlTheme |
| 手柄主题 | src/AtomUI.Desktop.Controls/Splitter/Themes/SplitterHandleTheme.axaml |
SplitterHandle ControlTheme |
| 模板常量 | src/AtomUI.Desktop.Controls/Splitter/Themes/SplitterThemeConstants.cs |
模板部件名称常量 |
| 主题注册 | src/AtomUI.Desktop.Controls/Splitter/Themes/SplitterThemes.axaml |
主题合并入口 |
| Gallery 示例 | controlgallery/AtomUIGallery/ShowCases/Views/Layout/SplitterShowCase.axaml |
使用范例 |
模板结构
Splitter 模板
Border#Frame
└── SplitterPanel (PART_SplitterPanel)
├── 用户子面板 [0]
├── SplitterHandle [0] ← 自动生成
├── 用户子面板 [1]
├── SplitterHandle [1] ← 自动生成
└── 用户子面板 [2]
Splitter 的模板非常简洁——仅包含一个 SplitterPanel,所有子面板和手柄的管理都由 SplitterPanel 内部负责。
SplitterHandle 模板
Border (Background=Transparent, ClipToBounds=False)
└── Grid
├── Border#PART_HandleLine ← 分割线(贯穿全高/全宽)
├── Border#PART_HandleGrip ← 手柄握持区(中央小条)
└── Canvas#PART_CollapseIconsHost ← 折叠图标容器(ZIndex 高层)
├── IconButton#PART_CollapsePrevButton ← 向前折叠按钮
└── IconButton#PART_CollapseNextButton ← 向后折叠按钮
设计要点:
- 手柄区域的背景设为透明,实际可拖拽区域由
SplitTriggerSizeToken 控制,大于可见分割线宽度,提升拖拽易用性。 - 折叠按钮使用
Canvas容器定位,ClipToBounds=False,允许按钮超出手柄可见区域显示。 - 根据
Orientation属性动态切换手柄线条和握持区的方向布局。
模板部件常量
| 常量名 | 值 | 说明 |
|---|---|---|
SplitterThemeConstants.SplitterPanelPart |
"PART_SplitterPanel" |
内部布局面板 |
SplitterThemeConstants.HandleLinePart |
"PART_HandleLine" |
手柄分割线 |
SplitterThemeConstants.HandleGripPart |
"PART_HandleGrip" |
手柄握持区 |
SplitterThemeConstants.CollapseIconsHostPart |
"PART_CollapseIconsHost" |
折叠图标容器 |
SplitterThemeConstants.CollapsePrevButtonPart |
"PART_CollapsePrevButton" |
向前折叠按钮 |
SplitterThemeConstants.CollapseNextButtonPart |
"PART_CollapseNextButton" |
向后折叠按钮 |