# ScrollViewer 滚动视图
## 概述
ScrollViewer(滚动视图)是用于在有限的显示区域内查看超出边界内容的容器控件。当内容尺寸超过 ScrollViewer 的可视区域时,用户可以通过滚动条(水平和/或垂直)来浏览全部内容。ScrollViewer 是构建可滚动列表、长表单、文档阅读器等场景的基础设施组件。
AtomUI 的 `ScrollViewer` 是对 Avalonia 原生 `ScrollViewer` 的**主题化增强**,通过 Design Token 系统提供了统一的滚动条视觉风格,并增加了**极简模式(LiteMode)**、**自动隐藏(AllowAutoHide)**以及**平滑过渡动画**等能力。
> **注意**:ScrollViewer 是 AtomUI 的扩展控件,在 Ant Design 中没有直接对应的组件。其设计灵感来自 macOS 系统滚动条和现代 UI 框架的滚动条风格。
---
## 设计原理
### 滚动视图的设计目标
滚动视图是 UI 框架中最基础的布局容器之一。AtomUI 对滚动视图的设计目标是:
1. **视觉一致性**:所有使用 ScrollViewer 的地方(列表、树、菜单、内容面板等)呈现统一的滚动条风格。
2. **不干扰内容**:滚动条应尽可能低调,不抢占内容空间,不遮挡关键信息。
3. **智能显隐**:根据使用场景,滚动条可以常驻显示,也可以在鼠标悬浮时自动出现。
4. **主题化**:滚动条的颜色、粗细、圆角等视觉属性均由 Design Token 控制,随主题(亮色/暗色)自动适配。
### 两种显示模式
AtomUI ScrollViewer 提供两种显示模式:
| 模式 | `IsLiteMode` | 行为描述 |
|---|---|---|
| **标准模式(Normal)** | `false`(默认) | 滚动条常驻显示,占据独立空间(不覆盖内容),滑块粗细为 `NormalModeThumbThickness`(默认 `SizeXS`) |
| **极简模式(Lite)** | `true` | 滚动条默认隐藏,鼠标进入 ScrollViewer 区域时淡入显示;滑块较细(`LiteModeThumbThickness = LineWidthBold`),悬浮或拖拽时膨胀为标准粗细;内容区域跨越滚动条空间,不为滚动条预留位置 |
### 自动隐藏机制
当 `AllowAutoHide = true` 时,ScrollViewer 会监听全局指针移动事件(通过 `IInputManager`),判断指针是否在 ScrollViewer 区域内:
- **鼠标进入区域**:滚动条不透明度渐变至 1.0(可见)
- **鼠标离开区域**:滚动条不透明度渐变至 0.0(隐藏)
- **滑块拖拽中**:即使鼠标离开区域,滚动条保持可见;拖拽结束后,如果鼠标不在区域内则隐藏
### 滚动条分隔符
当同时存在水平和垂直滚动条时,右下角会出现一个分隔面板(`ScrollBarsSeparator`),使用 `ColorBgContainer` 背景色。该面板在 `IsExpanded = true` 时显示,否则隐藏。
### Avalonia ScrollViewer 基础能力
AtomUI 的 `ScrollViewer` 继承自 Avalonia 框架的 `Avalonia.Controls.ScrollViewer`。理解 Avalonia ScrollViewer 的基础能力有助于理解 AtomUI 在其之上做了哪些扩展。
**Avalonia ScrollViewer 的核心职责:**
Avalonia 的 `ScrollViewer` 是一个专用容器控件,内部通过 `ScrollContentPresenter` 呈现子内容,并根据内容尺寸与可视区域的关系自动显示水平/垂直 `ScrollBar`。它支持滚动惯性(`IsScrollInertiaEnabled`)、延迟滚动(`IsDeferredScrollingEnabled`)、SnapPoints 对齐等高级特性。
**Avalonia ScrollViewer 提供的基础属性:**
| 属性 | 说明 |
|---|---|
| `HorizontalScrollBarVisibility` | 水平滚动条显示策略:`Disabled`、`Auto`(按需)、`Visible`(始终)、`Hidden`(隐藏但可滚动) |
| `VerticalScrollBarVisibility` | 垂直滚动条显示策略,选项同上 |
| `AllowAutoHide` | 是否允许滚动条在无交互时自动隐藏 |
| `IsScrollInertiaEnabled` | 是否启用触控/滚轮的滚动惯性效果 |
| `IsDeferredScrollingEnabled` | 延迟滚动模式,拖拽滑块时不实时滚动内容,释放后才更新 |
| `Offset` | 当前滚动偏移量(`Vector` 类型) |
| `Extent` | 内容的总尺寸 |
| `Viewport` | 可视区域尺寸 |
| `IsExpanded` | 滚动条是否处于展开状态(鼠标悬浮/拖拽时) |
| `BringIntoViewOnFocusChange` | 焦点变化时是否自动滚动到焦点元素 |
### AtomUI 的扩展设计
AtomUI `ScrollViewer` 在 Avalonia ScrollViewer 的基础上做了以下扩展:
| 扩展能力 | 实现方式 | 设计动机 |
|---|---|---|
| **极简模式** | `IsLiteMode` 附加属性 + 主题样式切换 | 提供更轻量、不干扰内容的滚动条体验 |
| **自定义 ScrollBar** | `atom:ScrollBar` 替代原生 `ScrollBar` | 统一滚动条视觉风格,支持 LiteMode 和 Token 系统 |
| **自定义 ScrollBarThumb** | `atom:ScrollBarThumb` 替代原生 `Thumb` | 支持方向感知、粗细动画、Token 驱动的背景色 |
| **滚动条透明度动画** | `ScrollBarOpacity` 内部属性 + `DoubleTransition` | 平滑的显隐过渡效果 |
| **全局指针监听** | 通过 `IInputManager` 订阅指针事件 | 精准判断指针是否在 ScrollViewer 区域内,控制滚动条显隐 |
| **Design Token** | `ScrollViewerToken` + `RegisterTokenResourceScope` | 所有视觉值从全局/组件级 Token 派生,支持主题切换 |
| **Avalonia 原生兼容** | `AvaScrollViewerTheme` + `AvaScrollBarTheme` | 同时为 Avalonia 原生 `ScrollViewer` / `ScrollBar` 提供主题,确保三方控件也有一致的滚动条风格 |
| **过渡动画** | `IMotionAwareControl` 接口 + `IsMotionEnabled` 属性 | 支持全局动画开关,可禁用滚动条的过渡效果 |
---
## 功能详解
### 极简模式(IsLiteMode)
极简模式是 AtomUI ScrollViewer 的特色功能,适用于侧边栏、导航菜单等不希望滚动条占据空间的场景:
- **滑块细线显示**:默认状态下,滑块以 `LiteModeThumbThickness`(= `SharedToken.LineWidthBold`,约 2-3px)的细线呈现
- **悬浮膨胀**:鼠标悬浮到滑块上时,滑块宽度/高度过渡到 `NormalModeThumbThickness`(= `SharedToken.SizeXS`,约 8px),方便拖拽操作
- **内容全覆盖**:内容区域的 Grid.ColumnSpan / Grid.RowSpan 被设为 2,使内容延伸到滚动条区域下方,滚动条叠加在内容之上
`IsLiteMode` 是一个**附加属性**(`AttachedProperty`),可以设置在 ScrollViewer 本身或使用 ScrollViewer 的其他控件上(例如 `atom:NavMenu`):
```xml
...
...
```
### 自动隐藏(AllowAutoHide)
`AllowAutoHide` 控制滚动条是否在无交互时自动隐藏:
- **`AllowAutoHide = true`(默认)**:滚动条根据鼠标位置自动显隐,内容区域覆盖滚动条空间
- **`AllowAutoHide = false`**:滚动条常驻显示,滚动条占据独立空间
### 过渡动画(Transitions)
ScrollViewer 系列控件实现了 `IMotionAwareControl` 接口,支持以下过渡动画:
| 控件 | 过渡属性 |
|---|---|
| `ScrollViewer` | `ScrollBarsSeparatorOpacity`、`ScrollBarOpacity` |
| `ScrollBarThumb` | `Background`(颜色过渡)、`Width`(垂直方向粗细变化)、`Height`(水平方向粗细变化) |
动画在 `OnLoaded` 时配置,`OnUnloaded` 时清除。通过 `IsMotionEnabled` 属性可全局开关:
```xml
```
### Avalonia 原生控件主题兼容
AtomUI 不仅为自己的 `atom:ScrollViewer` 和 `atom:ScrollBar` 提供主题,还为 Avalonia 原生的 `ScrollViewer` 和 `ScrollBar` 提供了兼容主题(`AvaScrollViewerTheme.axaml` 和 `AvaScrollBarTheme.axaml`)。这意味着:
- 即使三方控件内部使用原生 Avalonia `ScrollViewer`,也会呈现 AtomUI 的滚动条风格
- 原生滚动条使用标准粗细,不支持 LiteMode
- 同样通过 `ScrollViewerToken` 获取颜色和间距值
---
## 与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
| 滚动视图 | Ant Design 无独立 ScrollViewer 组件 | ✅ 独立控件 | — 不适用 |
| 自定义滚动条样式 | 部分组件(如 Table、List)内置虚拟滚动 | ✅ 统一的 Token 驱动滚动条风格 | ✅ 风格一致性更好 |
| 自动隐藏滚动条 | 由浏览器/操作系统控制 | ✅ `AllowAutoHide` + 极简模式 | ✅ 增强能力 |
| 滚动条颜色随主题 | CSS 有限支持 | ✅ Token 系统自动适配亮色/暗色主题 | ✅ 增强能力 |
---
## 继承关系
### ScrollViewer
```
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.ContentControl
└── Avalonia.Controls.ScrollViewer
└── AtomUI.Controls.Commons.AbstractScrollViewer (IMotionAwareControl)
└── AtomUI.Desktop.Controls.ScrollViewer
```
### ScrollBar
```
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.Primitives.RangeBase
└── Avalonia.Controls.Primitives.ScrollBar
└── AtomUI.Controls.Commons.AbstractScrollBar (IMotionAwareControl)
└── AtomUI.Desktop.Controls.ScrollBar
```
### ScrollBarThumb
```
Avalonia.Controls.Primitives.TemplatedControl
└── Avalonia.Controls.Primitives.Thumb
└── AtomUI.Controls.Commons.AbstractScrollBarThumb (IMotionAwareControl)
└── AtomUI.Desktop.Controls.ScrollBarThumb
```
**各层级职责划分:**
| 层级 | 提供的能力 |
|---|---|
| `Avalonia.Controls.ScrollViewer` | 滚动逻辑、`ScrollContentPresenter`、滚动条可见性控制、惯性滚动、SnapPoints、`AllowAutoHide` |
| `AbstractScrollViewer` (AtomUI.Controls) | 过渡动画配置(`IMotionAwareControl`)、全局指针监听实现自动显隐、拖拽状态追踪、`ScrollBarOpacity` / `ScrollBarsSeparatorOpacity` 内部属性 |
| `ScrollViewer` (AtomUI.Desktop.Controls) | `IsLiteMode` 附加属性、Token 作用域注册 |
| `Avalonia.Controls.Primitives.ScrollBar` | 滚动条基础逻辑、`Value` / `Minimum` / `Maximum`、`ViewportSize`、`AllowAutoHide`、`IsExpanded` |
| `AbstractScrollBar` (AtomUI.Controls) | `IsEffectiveExpanded` 内部状态、`IMotionAwareControl` 接口、自动隐藏状态管理 |
| `ScrollBar` (AtomUI.Desktop.Controls) | `IsLiteMode` 属性、LiteMode 下的展开逻辑覆写、Token 作用域注册 |
| `Avalonia.Controls.Primitives.Thumb` | 拖拽行为(`DragStarted` / `DragDelta` / `DragCompleted` 事件) |
| `AbstractScrollBarThumb` (AtomUI.Controls) | `Orientation` 属性、过渡动画(Background/Width/Height)、`IMotionAwareControl`、`IsExpanded` 内部属性 |
| `ScrollBarThumb` (AtomUI.Desktop.Controls) | `IsLiteMode` 属性 |
**实现的共享接口:**
| 接口 | 定义位置 | 实现控件 | 作用 |
|---|---|---|---|
| `IMotionAwareControl` | `AtomUI.Controls` | `AbstractScrollViewer`、`AbstractScrollBar`、`AbstractScrollBarThumb` | 支持 `IsMotionEnabled` 属性,全局动画开关 |
---
## 源码位置
### 基类层(AtomUI.Controls)—— 设备无关
| 文件路径 | 说明 |
|---|---|
| `src/AtomUI.Controls/ScrollViewer/AbstractScrollViewer.cs` | 滚动视图抽象基类:动画、自动隐藏逻辑、指针监听 |
| `src/AtomUI.Controls/ScrollViewer/AbstractScrollBar.cs` | 滚动条抽象基类:展开状态管理、动画开关 |
| `src/AtomUI.Controls/ScrollViewer/AbstractScrollBarThumb.cs` | 滚动条滑块抽象基类:方向感知、粗细/背景色过渡动画 |
| `src/AtomUI.Controls/ScrollViewer/ScrollBarReflectionExtensions.cs` | 内部工具:通过反射访问 Avalonia ScrollBar 的私有成员 |
| `src/AtomUI.Controls/ScrollViewer/ScrollBarRepeatButton.cs` | 滚动条翻页按钮(透明、不可聚焦) |
### 平台层(AtomUI.Desktop.Controls)—— 桌面端具体实现
| 文件路径 | 说明 |
|---|---|
| `src/AtomUI.Desktop.Controls/ScrollViewer/ScrollViewer.cs` | 桌面端 ScrollViewer:`IsLiteMode` 附加属性、Token 注册 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/ScrollBar.cs` | 桌面端 ScrollBar:`IsLiteMode` 属性、LiteMode 展开逻辑覆写 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/ScrollBarThumb.cs` | 桌面端 ScrollBarThumb:`IsLiteMode` 属性 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/ScrollViewerToken.cs` | 组件级 Design Token |
| `src/AtomUI.Desktop.Controls/ScrollViewer/Themes/ScrollViewerTheme.axaml` | AtomUI ScrollViewer 主题 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/Themes/ScrollBarTheme.axaml` | AtomUI ScrollBar 主题 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/Themes/ScrollBarThumbTheme.axaml` | AtomUI ScrollBarThumb 主题 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/Themes/ScrollBarThumbTheme.cs` | ScrollBarThumb 主题 code-behind |
| `src/AtomUI.Desktop.Controls/ScrollViewer/Themes/AvaScrollViewerTheme.axaml` | Avalonia 原生 ScrollViewer 兼容主题 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/Themes/AvaScrollBarTheme.axaml` | Avalonia 原生 ScrollBar 兼容主题 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/Themes/ScrollBarRepeatButtonTheme.axaml` | ScrollBarRepeatButton 主题 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/Themes/ScrollViewerThemeConstants.cs` | 模板部件名称常量 |
| `src/AtomUI.Desktop.Controls/ScrollViewer/Themes/ScrollViewerThemes.axaml` | 主题资源字典汇总注册 |
---
## 模板结构
### ScrollViewer 模板
```
Grid (ColumnDefinitions="*,Auto", RowDefinitions="*,Auto")
├── ScopeAwareOverlayLayerPanel (Grid.Row=0, Grid.Column=0)
│ └── ScrollContentPresenter (PART_ContentPresenter) ← 内容呈现器
│ └── ScrollGestureRecognizer ← 触控/滚轮手势识别
├── ScrollBar#PART_HorizontalScrollBar ← 水平滚动条 (Grid.Row=1, Grid.Column=0)
├── ScrollBar#PART_VerticalScrollBar ← 垂直滚动条 (Grid.Row=0, Grid.Column=1)
└── Panel#ScrollBarsSeparator ← 右下角分隔面板 (Grid.Row=1, Grid.Column=1)
```
**布局说明:**
- 内容区域位于左上(0,0),水平滚动条位于下方(1,0),垂直滚动条位于右侧(0,1),分隔面板位于右下角(1,1)
- 当 `AllowAutoHide = true` 或 `IsLiteMode = true` 时,内容区域的 `Grid.ColumnSpan` 和 `Grid.RowSpan` 被设为 2,使内容延伸到滚动条区域
### ScrollBar 模板
```
Border#Frame
└── Grid#RootLayout
└── Track (PART_Track)
├── Track.DecreaseButton → ScrollBarRepeatButton (PART_PageUpButton) ← 向上翻页区域
├── Track.Thumb → ScrollBarThumb (PART_Thumb) ← 滑块
└── Track.IncreaseButton → ScrollBarRepeatButton (PART_PageDownButton) ← 向下翻页区域
```
### ScrollBarThumb 模板
```
Border#Frame (Background, CornerRadius, Margin) ← 滑块可视外观
```
### 模板部件常量
| 常量名 | 值 | 说明 |
|---|---|---|
| `ScrollViewerThemeConstants.PageUpButtonPart` | `"PART_PageUpButton"` | 向上/向左翻页按钮 |
| `ScrollViewerThemeConstants.PageDownButtonPart` | `"PART_PageDownButton"` | 向下/向右翻页按钮 |
| `ScrollViewerThemeConstants.TrackPart` | `"PART_Track"` | 滚动条轨道 |
| `ScrollViewerThemeConstants.ThumbPart` | `"PART_Thumb"` | 滚动条滑块 |