14 KiB
Skeleton 骨架屏
概述
骨架屏(Skeleton)在数据加载完成前展示页面的大致结构,给予用户「数据正在加载」的感知。它用灰色占位块模拟真实内容的布局,避免页面在加载过程中呈现空白,有效减少用户的等待焦虑。
AtomUI 的 Skeleton 控件完整实现了 Ant Design 5.0 Skeleton 的设计规范,在 .NET / Avalonia 平台上提供与 Ant Design React 版一致的视觉体验和流光动画效果。
设计原理
Ant Design 的骨架屏设计哲学
Ant Design 对骨架屏的定位是:「在需要等待加载内容的位置提供一个占位图形组合」。骨架屏的核心价值不是替代 Loading Spinner,而是提供内容结构的预期暗示——让用户在数据到达之前就能预判页面布局,降低认知负荷。
两种使用模式:
| 模式 | 设计意图 | 典型用途 |
|---|---|---|
| 组合骨架屏(Skeleton) | 整体占位,模拟头像 + 标题 + 段落的典型内容结构。加载完成后切换为真实内容 | 列表项加载、卡片内容加载、文章详情初始化 |
| 独立元素骨架屏 | 单个 UI 元素占位,模拟按钮/输入框/头像/图片的外形。通常嵌入自定义布局 | 表单加载、操作栏加载、头像列表加载 |
独立元素类型:
| 元素 | 对应控件 | 模拟对象 |
|---|---|---|
| 按钮骨架 | SkeletonButton |
按钮占位(支持方形/圆形/胶囊形) |
| 头像骨架 | SkeletonAvatar |
头像占位(支持圆形/方形) |
| 输入框骨架 | SkeletonInput |
输入框占位 |
| 图片骨架 | SkeletonImage |
图片占位(内嵌图片图标) |
| 自定义骨架 | SkeletonNode |
自定义内容占位(可放入任意子元素) |
流光动画:
当 IsActive=True 时,骨架块呈现从左到右的渐变流光效果(shimmer),暗示数据正在加载中。动画通过三阶段渐变色(Start → Middle → End)的无限循环实现。
Avalonia 基础
Skeleton 家族的所有控件均继承自 Avalonia 的 TemplatedControl,不依赖 Avalonia 内置的 Skeleton 控件(Avalonia 没有内置骨架屏组件)。所有骨架屏控件均为 AtomUI 从零构建。
AtomUI 的架构设计
AtomUI Skeleton 采用控件族设计模式,通过抽象基类和继承体系实现代码复用:
| 设计决策 | 实现方式 | 设计动机 |
|---|---|---|
| 统一动画基础 | AbstractSkeleton 抽象基类 |
所有骨架控件共享流光动画逻辑,避免重复实现 |
| 动画同步 | Follow/UnFollow 机制 |
组合骨架屏中的子元素(头像、标题、段落)跟随父级动画节奏,确保视觉一致 |
| 组合模式 | Skeleton 内嵌 SkeletonAvatar + SkeletonTitle + SkeletonParagraph |
对齐 Ant Design 的组合骨架屏结构 |
| 独立元素基类 | SkeletonElement 抽象类 |
SkeletonButton / SkeletonInput 共享尺寸和 Block 逻辑 |
| 内容切换 | IsLoading + Content 属性 |
Skeleton 在加载态显示骨架,加载完成后显示真实内容 |
| 灵活尺寸 | Dimension 结构体(支持像素和百分比) |
标题宽度、段落行宽支持绝对值和相对值 |
| Design Token | SkeletonToken + RegisterTokenResourceScope |
所有视觉值从全局/组件级 Token 派生 |
功能详解
组合骨架屏(Skeleton)
Skeleton 控件是最常用的骨架屏形式,它内部组合了头像、标题和段落三个部分:
- 头像区域(
SkeletonAvatar):通过IsShowAvatar控制是否显示,支持AvatarShape(Circle/Square)和AvatarSizeType(Large/Middle/Small/Custom) - 标题区域(
SkeletonTitle):通过IsShowTitle控制是否显示,宽度由TitleWidth控制(默认 50%) - 段落区域(
SkeletonParagraph):通过IsShowParagraph控制是否显示,行数由ParagraphRows控制,最后一行宽度由ParagraphLastLineWidth控制
主题会根据头像、标题、段落的显示组合自动调整默认值:
- 有头像 + 有段落:标题宽度默认 50%
- 无头像 + 有段落:标题宽度默认 38%
- 无头像 + 无标题:最后一行宽度默认 61%
- 无头像 + 有标题:段落行数默认 3
内容切换(IsLoading)
Skeleton 控件支持包裹真实内容。当 IsLoading=True 时显示骨架占位,IsLoading=False 时显示 Content 中的真实内容:
<atom:Skeleton IsLoading="{Binding IsDataLoading}">
<TextBlock>真实内容</TextBlock>
</atom:Skeleton>
流光动画(IsActive)
所有骨架控件均支持 IsActive 属性。启用后,骨架块呈现从左到右的渐变流光效果:
- 动画使用三阶段
LinearGradientBrush(Start → Middle → End) - 动画时长由
MotionDurationToken 控制(默认 1.4 秒) - 使用
CubicEaseOut缓动曲线 - 动画无限循环直到
IsActive设为false或控件离开视觉树
动画同步(Follow 机制)
在组合骨架屏中,Skeleton 作为动画主控,子组件(SkeletonAvatar、SkeletonTitle、SkeletonParagraph)通过 Follow(this) 绑定到父级的 AnimationLayerFill 和 IsActive 属性,确保所有骨架块的流光动画保持同步。
独立元素骨架屏
独立元素骨架屏用于自定义布局中单独使用,每个元素各自管理动画:
| 控件 | 默认宽度 | 默认高度 | 形状支持 |
|---|---|---|---|
SkeletonButton |
Height × 2 |
ControlHeight* |
Square / Round / Circle |
SkeletonInput |
Height × 5 |
ControlHeight* |
— |
SkeletonAvatar |
ControlHeight* |
ControlHeight* |
Circle / Square |
SkeletonImage |
ImageContainerSize |
ImageContainerSize |
— |
SkeletonNode |
ImageContainerSize |
ImageContainerSize |
—(可放入自定义内容) |
*高度根据SizeType(Large/Middle/Small)从ControlHeightLG/ControlHeight/ControlHeightSM获取。
圆角控制(IsRound)
Skeleton 和 SkeletonParagraph 的 IsRound 属性控制段落行的圆角样式:
false(默认):使用BlockRadius(BorderRadiusSM)true:使用ParagraphLineRoundCornerRadius(行高的一半,形成胶囊形)
尺寸系统(Dimension)
TitleWidth、ParagraphLastLineWidth、ParagraphLineWidths 等属性使用 Dimension 结构体,支持两种单位:
- 百分比:
new Dimension(50, DimensionUnitType.Percentage)或 AXAML 中写"50%" - 像素:
new Dimension(200, DimensionUnitType.Pixel)或 AXAML 中写"200"
与 Ant Design 规范的对齐程度
| 特性 | Ant Design (React) | AtomUI (Avalonia) | 对齐情况 |
|---|---|---|---|
| 基本骨架屏 | ✅ <Skeleton /> |
✅ <Skeleton IsLoading="True" /> |
✅ 完全对齐 |
| 头像 + 标题 + 段落 | ✅ avatar / title / paragraph |
✅ IsShowAvatar / IsShowTitle / IsShowParagraph |
✅ 完全对齐 |
| 流光动画 | ✅ active |
✅ IsActive |
✅ 完全对齐 |
| 内容包裹 | ✅ loading + children |
✅ IsLoading + Content |
✅ 完全对齐 |
| 段落行数 | ✅ paragraph.rows |
✅ ParagraphRows |
✅ 完全对齐 |
| 段落行宽 | ✅ paragraph.width |
✅ ParagraphLineWidths / ParagraphLastLineWidth |
✅ 完全对齐 |
| 标题宽度 | ✅ title.width |
✅ TitleWidth |
✅ 完全对齐 |
| 头像形状/尺寸 | ✅ avatar.shape / avatar.size |
✅ AvatarShape / AvatarSizeType / AvatarSize |
✅ 完全对齐 |
| 圆角模式 | ✅ round |
✅ IsRound |
✅ 完全对齐 |
| 按钮骨架 | ✅ Skeleton.Button |
✅ SkeletonButton |
✅ 完全对齐 |
| 头像骨架 | ✅ Skeleton.Avatar |
✅ SkeletonAvatar |
✅ 完全对齐 |
| 输入框骨架 | ✅ Skeleton.Input |
✅ SkeletonInput |
✅ 完全对齐 |
| 图片骨架 | ✅ Skeleton.Image |
✅ SkeletonImage |
✅ 完全对齐 |
| 自定义节点 | ✅ Skeleton.Node |
✅ SkeletonNode |
✅ 完全对齐 |
| Block 模式 | ✅ block |
✅ IsBlock |
✅ 完全对齐 |
继承关系
Avalonia.Controls.Primitives.TemplatedControl
└── AbstractSkeleton ← 流光动画基类(所有骨架控件共享)
├── Skeleton ← 组合骨架屏(头像 + 标题 + 段落 + 内容切换)
├── SkeletonLine ← 单行骨架线条(段落的组成单元)
│ └── SkeletonTitle ← 标题骨架(SkeletonLine 子类,默认宽度 50%)
├── SkeletonParagraph ← 段落骨架(动态生成多个 SkeletonLine)
├── SkeletonAvatar ← 头像骨架(支持圆形/方形 + 三种尺寸)
├── SkeletonElement ← 独立元素骨架基类(尺寸 + Block)
│ ├── SkeletonButton ← 按钮骨架(方形/圆形/胶囊形)
│ └── SkeletonInput ← 输入框骨架
└── SkeletonImage ← 图片骨架(内嵌图片图标)
└── SkeletonNode ← 自定义节点骨架(可放入任意内容)
各层级职责划分:
| 层级 | 提供的能力 |
|---|---|
TemplatedControl |
Avalonia 模板化控件基础 |
AbstractSkeleton |
流光动画逻辑(IsActive、MotionDuration、动画构建/启停)、Follow/UnFollow 同步机制、动画渐变色属性 |
Skeleton |
组合骨架屏:内嵌 SkeletonAvatar + SkeletonTitle + SkeletonParagraph,IsLoading + Content 内容切换,Token 注册 |
SkeletonElement |
独立元素基类:SizeType(可自定义尺寸)、IsBlock(撑满宽度) |
SkeletonButton |
按钮骨架:Shape(Square/Round/Circle),自动计算宽度和圆角 |
SkeletonInput |
输入框骨架:默认宽度 = 高度 × 5 |
SkeletonAvatar |
头像骨架:Shape(Circle/Square)、Size(自定义尺寸)、SizeType |
SkeletonLine |
单行骨架:LineWidth(Dimension)、IsRound,由 SkeletonParagraph 动态创建 |
SkeletonTitle |
标题骨架:继承 SkeletonLine,默认 LineWidth=50% |
SkeletonParagraph |
段落骨架:动态创建 Rows 个 SkeletonLine,支持 LastLineWidth 和 LineWidths |
SkeletonImage |
图片骨架:固定尺寸 + 内嵌 ImageFilled 图标 |
SkeletonNode |
自定义节点:继承 SkeletonImage,支持 Content / ContentTemplate |
实现的共享接口:
| 接口 | 实现控件 | 定义位置 | 作用 |
|---|---|---|---|
ICustomizableSizeTypeAware |
SkeletonElement、SkeletonAvatar |
AtomUI.Controls.Shared |
支持 CustomizableSizeType(Large/Middle/Small/Custom)四种尺寸 |
注意:Skeleton 系列控件全部定义在
AtomUI.Desktop.Controls中,不存在AtomUI.Controls基类层的设备无关抽象。这是因为骨架屏的渲染和动画行为与桌面平台紧密相关。
源码位置
| 层级 | 文件路径 | 说明 |
|---|---|---|
| 动画基类 | src/AtomUI.Desktop.Controls/Skeleton/AbstractSkeleton.cs |
流光动画逻辑、Follow 机制 |
| 组合骨架 | src/AtomUI.Desktop.Controls/Skeleton/Skeleton.cs |
主骨架屏控件 |
| 元素基类 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonElement.cs |
独立元素基类(SizeType + IsBlock) |
| 按钮骨架 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonButton.cs |
按钮占位 |
| 输入框骨架 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonInput.cs |
输入框占位 |
| 头像骨架 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonAvatar.cs |
头像占位 |
| 行骨架 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonLine.cs |
单行占位线条 |
| 标题骨架 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonTitle.cs |
标题占位 |
| 段落骨架 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonParagraph.cs |
段落占位(多行) |
| 图片骨架 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonImage.cs |
图片占位 |
| 节点骨架 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonNode.cs |
自定义节点占位 |
| Token 定义 | src/AtomUI.Desktop.Controls/Skeleton/SkeletonToken.cs |
组件级 Design Token |
| 主题模板 | src/AtomUI.Desktop.Controls/Skeleton/Themes/SkeletonTheme.axaml |
Skeleton ControlTheme |
| 抽象主题 | src/AtomUI.Desktop.Controls/Skeleton/Themes/AbstractSkeletonTheme.axaml |
通用骨架主题基类 |
| 主题常量 | src/AtomUI.Desktop.Controls/Skeleton/Themes/SkeletonThemeConstants.cs |
模板部件名称常量 |
| Gallery 示例 | controlgallery/AtomUIGallery/ShowCases/Views/Feedback/SkeletonShowCase.axaml |
使用范例 |
模板结构
Skeleton(组合骨架屏)
Panel
├── DockPanel (IsVisible = !IsContentVisible) ← 骨架占位层
│ ├── SkeletonAvatar#PART_Avatar (DockPanel.Dock=Left) ← 头像区域
│ └── StackPanel#PART_Content (Orientation=Vertical) ← 内容区域
│ ├── SkeletonTitle#PART_Title ← 标题行
│ └── SkeletonParagraph#PART_Paragraph ← 段落区域
└── ContentPresenter (IsVisible = IsContentVisible) ← 真实内容层
AbstractSkeleton(通用骨架基础模板)
Panel#PART_RootLayout
├── Border#PART_ActiveAnimationLayer ← 流光动画层(IsActive 时可见)
│ Background = AnimationLayerFill ← 渐变画刷(动画驱动)
└── Border#PART_ContentLayer ← 静态背景层(!IsActive 时可见)
Background = GradientFromColor ← 纯色填充
模板部件常量
| 常量 | 值 | 所属 | 说明 |
|---|---|---|---|
AbstractSkeletonThemeConstants.RootLayoutPart |
"PART_RootLayout" |
通用基础 | 根布局面板 |
AbstractSkeletonThemeConstants.ActiveAnimationLayerPart |
"PART_ActiveAnimationLayer" |
通用基础 | 流光动画层 |
AbstractSkeletonThemeConstants.ContentLayerPart |
"PART_ContentLayer" |
通用基础 | 静态背景层 |
SkeletonThemeConstants.AvatarPart |
"PART_Avatar" |
Skeleton | 头像组件 |
SkeletonThemeConstants.TitlePart |
"PART_Title" |
Skeleton | 标题组件 |
SkeletonThemeConstants.ContentPart |
"PART_Content" |
Skeleton | 内容面板 |
SkeletonThemeConstants.ParagraphPart |
"PART_Paragraph" |
Skeleton | 段落组件 |
SkeletonParagraphThemeConstants.LineLayoutPart |
"PART_LineLayout" |
SkeletonParagraph | 行布局面板 |