13 KiB
ImagePreviewer 图片预览
概述
图片预览(ImagePreviewer)用于预览和浏览图片。支持缩放、旋转、水平/垂直翻转、全屏查看、适应窗口/原始尺寸切换、鼠标拖拽移动、多图切换(键盘左右箭头及按钮)等功能。同时支持 SVG 和位图(PNG、JPEG、WebP 等)格式。
AtomUI 的 ImagePreviewer 对应 Ant Design 5.0 Image 组件,在 .NET / Avalonia 平台上提供封面预览 → 弹窗查看的完整图片浏览体验。
AtomUI 提供两个公共控件类:
| 控件 | 用途 |
|---|---|
ImagePreviewer |
单图预览 — 显示一张封面图,点击后弹出预览对话框。支持多张图片轮播(从一张封面预览多张) |
ImageGroupPreviewer |
多图组预览 — 以网格/列表形式显示多张封面缩略图,点击任意一张弹出预览对话框并定位到对应图片 |
设计原理
Ant Design 的 Image 设计
Ant Design 的 Image 组件采用 封面 → 遮罩 → 预览层 的三层交互模型:
- 封面层:展示图片缩略图,鼠标悬浮时显示半透明遮罩和"预览"提示文字
- 预览层:点击后弹出全屏/弹窗式预览,提供缩放、旋转、翻转、多图切换等操作工具栏
- 图片组:
Image.PreviewGroup将多张图片组合,共享一个预览层
AtomUI 忠实复刻了这套交互模型,并将其适配为桌面窗口体验:预览层以独立 Window 形式呈现,拥有标题栏工具栏和浮动工具栏双操作区域。
AtomUI 的架构设计
AtomUI 的 ImagePreviewer 采用 控件 → 对话框 → 查看器 的分层架构:
用户交互层(公共)
├── ImagePreviewer → 单图封面控件(公共)
└── ImageGroupPreviewer → 多图组封面控件(公共)
↓ 点击触发
预览窗口层(内部)
├── ImagePreviewerDialog → 预览对话框窗口(继承 Window)
│ ├── ImagePreviewToolbar → 标题栏工具栏(上/下一张、缩放、旋转、翻转)
│ └── ImageViewer → 图片查看器
│ ├── ImagePreviewRenderer → 图片渲染器(支持 SVG + Bitmap)
│ ├── ImagePreviewNavButton → 左/右切换按钮
│ └── ImagePreviewFloatToolbar → 浮动工具栏(缩放、旋转、翻转、索引指示)
封面层(内部)
└── ImagePreviewerCover → 封面展示 + 遮罩动画
扩展设计
| 扩展能力 | 实现方式 | 设计动机 |
|---|---|---|
| SVG + 位图双格式 | PreviewImageSource 自动检测 .svg 扩展名 |
桌面应用中 SVG 图标/插图与位图照片混用是常见场景 |
| 封面遮罩动画 | ImagePreviewerCover + MaskOpacity 过渡 |
复刻 Ant Design 的悬浮遮罩淡入效果 |
| 双工具栏 | 标题栏 ImagePreviewToolbar + 浮动 ImagePreviewFloatToolbar |
标题栏工具栏操作切换图片时保留旋转/翻转状态,浮动工具栏切换时重置状态(对齐 Ant Design 行为) |
| 窗口化预览 | ImagePreviewerDialog 继承 Window |
桌面端利用原生窗口能力(移动、缩放、最大化、置顶) |
| 对话框定位 | DialogHorizontalStartupLocation / DialogVerticalStartupLocation |
支持居中、左/右/上/下对齐,以及自定义偏移量 |
| 模态/非模态 | IsDialogModal 属性 |
非模态(默认)允许同时操作主窗口;模态阻塞主窗口交互 |
| 图片拖拽 | IsImageMovable + 鼠标事件处理 |
缩放后拖拽查看图片细节 |
| 缩放控制 | ImageScaleStep / ImageMinScale / ImageMaxScale |
精确控制缩放行为,支持鼠标滚轮缩放 |
| 键盘导航 | Left / Right 方向键切换图片 |
无需鼠标即可浏览 |
| 适应窗口 | FitToWindow / OneToOne 切换 |
在窗口适应和原始尺寸之间自由切换 |
| Design Token | ImagePreviewerToken |
所有视觉值从全局/组件级 Token 派生 |
功能详解
封面交互
ImagePreviewer 展示一张封面图片。鼠标悬浮时,半透明遮罩(MaskBgColor)以动画淡入,显示"预览"提示(眼睛图标 + 本地化文字)。点击封面打开预览对话框。
- 可通过
CoverImageSrc指定不同于预览图的封面图 - 可通过
IsShowCoverMask控制是否显示遮罩 - 可通过
CoverIndicatorContent/CoverIndicatorContentTemplate自定义遮罩内容
ImageGroupPreviewer 使用 ItemsControl 展示多张封面缩略图,点击任意一张直接定位到该图片的预览。
预览对话框
预览对话框(ImagePreviewerDialog)是一个独立的 Window,初始尺寸为屏幕工作区的 70%。提供:
- 标题栏工具栏:上/下一张、缩小、放大、适应窗口/原始尺寸、水平翻转、垂直翻转、左旋转、右旋转
- 浮动工具栏:底部居中,圆角胶囊形,含相同操作按钮 + 图片索引指示器(
1 / 3格式) - 左/右导航按钮:叠加在图片两侧,半透明圆形按钮
缩放与变换
- 缩放步进由
ImageScaleStep控制(默认 0.5,即每次缩放 50%) - 缩放范围由
ImageMinScale(默认 1.0)和ImageMaxScale(默认 50.0)限制 - 鼠标滚轮支持平滑缩放(步进根据滚轮增量动态调整,范围 0.5% ~ 12%)
- 缩放后图片位置自动约束在可视区域内
图片切换策略
标题栏工具栏和浮动工具栏在切换图片时的变换保留策略不同:
| 工具栏位置 | 切换时旋转/翻转 | 动画 |
|---|---|---|
| 标题栏 | 保留 | 有动画 |
| 浮动栏 | 重置 | 无动画(即时切换) |
这一设计对齐 Ant Design 的行为:标题栏操作更"持久",浮动栏操作更"轻量"。
与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
| 封面 + 遮罩 | ✅ 悬浮显示遮罩 | ✅ MaskOpacity 动画 |
✅ 完全对齐 |
| 预览弹窗 | ✅ 全屏遮罩层 | ✅ 独立 Window | ⚠️ 桌面窗口替代 Web 遮罩层 |
| 缩放 | ✅ 放大/缩小/滚轮 | ✅ ScaleStep + 滚轮 |
✅ 完全对齐 |
| 旋转 | ✅ 左旋/右旋 | ✅ 90° 步进 | ✅ 完全对齐 |
| 翻转 | ✅ 水平/垂直 | ✅ ScaleX / ScaleY 翻转 |
✅ 完全对齐 |
| 适应窗口/原始尺寸 | ✅ 切换 | ✅ FitToWindow / OneToOne |
✅ 完全对齐 |
| 多图组 | ✅ Image.PreviewGroup |
✅ ImageGroupPreviewer |
✅ 完全对齐 |
| 键盘导航 | ✅ 左/右方向键 | ✅ Key.Left / Key.Right |
✅ 完全对齐 |
| 拖拽移动 | ✅ 放大后可拖拽 | ✅ IsImageMovable |
✅ 完全对齐 |
| 自定义预览图 | ✅ preview 属性 |
✅ CoverImageSrc |
✅ 完全对齐 |
| 容错图片 | ✅ fallback |
✅ FallbackImageSrc |
✅ 完全对齐 |
| SVG 支持 | ❌ | ✅ 自动检测 | ✅ AtomUI 增强 |
| 模态/非模态 | ❌(Web 无此概念) | ✅ IsDialogModal |
✅ 桌面增强 |
| 窗口移动/缩放/最大化 | ❌(Web 无此概念) | ✅ 原生窗口 | ✅ 桌面增强 |
继承关系
Avalonia.Controls.Primitives.TemplatedControl
└── AtomUI.Desktop.Controls.AbstractImagePreviewer (IMotionAwareControl)
├── AtomUI.Desktop.Controls.ImagePreviewer ← 单图预览
└── AtomUI.Desktop.Controls.ImageGroupPreviewer ← 多图组预览
注意:与大多数 AtomUI 控件不同,ImagePreviewer 的
AbstractImagePreviewer基类位于AtomUI.Desktop.Controls而非AtomUI.Controls,因为预览对话框(Window)是桌面特有概念。
各层级职责划分:
| 层级 | 提供的能力 |
|---|---|
AbstractImagePreviewer |
图片源管理、预览对话框生命周期(打开/关闭/事件)、对话框定位配置、缩放参数传递、SVG/Bitmap 加载 |
ImagePreviewer |
封面图管理(CoverImageSrc)、封面遮罩和指示器内容(CoverIndicatorContent)、自动选取首张图作为封面 |
ImageGroupPreviewer |
多图缩略图列表展示(ItemsPanel)、点击定位到对应图片索引 |
实现的共享接口:
| 接口 | 定义位置 | 作用 |
|---|---|---|
IMotionAwareControl |
AtomUI.Controls |
支持 IsMotionEnabled 过渡动画开关 |
源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 基类 | src/AtomUI.Desktop.Controls/ImagePreviewer/AbstractImagePreviewer.cs |
图片预览基类 |
| 单图控件 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewer.cs |
单图预览控件 |
| 多图组控件 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImageGroupPreviewer.cs |
多图组预览控件 |
| 事件参数 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImageFitToWindowEventArgs.cs |
适应窗口事件参数 |
| Token 定义 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewerToken.cs |
组件级 Design Token |
| 预览对话框 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewerDialog.cs |
预览窗口(internal) |
| 图片查看器 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImageViewer.cs |
图片查看器(internal) |
| 封面组件 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewerCover.cs |
封面展示(internal) |
| 导航按钮 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewNavButton.cs |
左/右导航按钮(internal) |
| 工具栏基类 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewBaseToolbar.cs |
工具栏基类(internal) |
| 浮动工具栏 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewFloatToolbar.cs |
底部浮动工具栏(internal) |
| 标题栏工具栏 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewToolbar.cs |
标题栏工具栏(internal) |
| 图片渲染器 | src/AtomUI.Desktop.Controls/ImagePreviewer/ImagePreviewRenderer.cs |
SVG/Bitmap 渲染(internal) |
| 图片源 | src/AtomUI.Desktop.Controls/ImagePreviewer/PreviewImageSource.cs |
图片数据封装(internal) |
| 模板常量 | src/AtomUI.Desktop.Controls/ImagePreviewer/Themes/ImagePreviewerThemeConstants.cs |
模板部件常量 |
| 本地化 en_US | src/AtomUI.Desktop.Controls/ImagePreviewer/Localization/en_US.cs |
英文语言资源 |
| 本地化 zh_CN | src/AtomUI.Desktop.Controls/ImagePreviewer/Localization/zh_CN.cs |
中文语言资源 |
| Gallery 示例 | controlgallery/AtomUIGallery/ShowCases/Views/DataDisplay/ImagePreviewerShowCase.axaml |
使用范例 |
模板结构
ImagePreviewer 模板
Border (背景、边框、圆角)
└── ImagePreviewerCover (封面展示 + 遮罩)
├── ImagePreviewRenderer (图片渲染)
└── Border#Mask (遮罩层)
└── ContentPresenter (指示器内容:眼睛图标 + "预览"文字)
ImageGroupPreviewer 模板
Border (背景、边框、圆角)
└── ItemsControl#PART_CoverItemsControl (封面列表)
└── [每项] ImagePreviewerCover (缩略图)
ImagePreviewerDialog 模板
Panel
├── Border#WindowFrame (窗口主框架)
│ └── Panel
│ ├── ContentPresenter#WindowFrameLayer
│ └── Border (圆角裁剪)
│ └── DialogLayerManager
│ └── DockPanel#PART_ContentLayout
│ ├── [DockPanel.Dock=Top] TitleBar (含 ImagePreviewToolbar)
│ └── [Fill] ImageViewer#PART_ImageViewer
└── WindowResizer (窗口拖拽调整大小)
ImageViewer 模板
Panel
├── Canvas#PART_ImageViewerScene
│ └── ImagePreviewRenderer#PART_ImageRenderer (图片渲染,支持变换)
├── ImagePreviewNavButton#PART_PreviousButton (左侧导航按钮)
├── ImagePreviewNavButton#PART_NextButton (右侧导航按钮)
├── ImagePreviewFloatToolbar (底部浮动工具栏)
└── WindowMediaQueryIndicator
模板部件常量
| 常量名 | 值 | 说明 |
|---|---|---|
CoverItemsControlPart |
"PART_CoverItemsControl" |
多图组封面列表 |
ImageViewerPart |
"PART_ImageViewer" |
图片查看器 |
ImageViewerScenePart |
"PART_ImageViewerScene" |
图片查看器画布 |
ImageRendererPart |
"PART_ImageRenderer" |
图片渲染器 |
PreviousButtonPart |
"PART_PreviousButton" |
上一张按钮 |
NextButtonPart |
"PART_NextButton" |
下一张按钮 |
ScaleDownButtonPart |
"PART_ScaleDownButton" |
缩小按钮 |
ScaleUpButtonPart |
"PART_ScaleUpButton" |
放大按钮 |
FitToWindowButtonPart |
"PART_FitToWindowButton" |
适应窗口/原始尺寸切换按钮 |
HorizontalFlipButtonPart |
"PART_HorizontalFlipButton" |
水平翻转按钮 |
VerticalFlipButtonPart |
"PART_VerticalFlipButton" |
垂直翻转按钮 |
RotateLeftButtonPart |
"PART_RotateLeftButton" |
左旋转按钮 |
RotateRightButtonPart |
"PART_RotateRightButton" |
右旋转按钮 |