11 KiB
Avatar 桌面版实现原理
本文档描述 Avatar 控件家族的源码 ownership、图片加载状态流、模板生命周期和资源释放规则。公共契约见 Avatar 控件家族架构设计,Token 语义见 Avatar Token 设计。
Popup 接入边界:AvatarGroup 负责业务状态和内容准备,内部 FlyoutHost 仅作为 relay 适配层,fold-count Flyout Popup 负责实际显示。模板重建或宿主切换时必须先释放旧 relay,再绑定新的 Popup;普通外点、Escape、失焦和业务关闭在 pinned 状态下被拦截,detach、窗口销毁、跨 TopLevel 和无效锚点必须走生命周期关闭并释放 Popup host。完整状态机见 Popup 钉住打开设计。
1. 实现定位
本文覆盖单头像、桌面 AvatarGroup、共享单图 controller、主题接入和图片租约生命周期。具体 StyledProperty 注册、Token 派生值和 AXAML selector 仍以源码为准;本文只记录跨文件协作和维护时不可破坏的实现边界。
2. 源码文件结构
AtomUI.Controls 拥有单头像实现:
src/AtomUI.Controls/Avatar/
├── AbstractAvatar.cs
├── Avatar.cs
├── AvatarPseudoClass.cs
├── AvatarShape.cs
├── AvatarToken.cs
└── Themes/AvatarTheme.axaml
AtomUI.Desktop.Controls 只拥有桌面组合控件:
src/AtomUI.Desktop.Controls/Avatar/
├── AvatarGroup.cs
├── AvatarGroupFoldInfo.cs
└── Themes/AvatarGroupTheme.axaml
Avatar 和 AvatarToken 不在 Desktop 包中保留副本。Desktop 通过对 Controls 的正常项目引用复用类型和 Token resource。
3. 核心类职责
AbstractAvatar:Avalonia 属性、图片 controller host、内容优先级、尺寸/形状归一、加载状态和模板生命周期。Avatar:可实例化的公共控件类型,不建立第二套状态。ImageLoadController:与AsyncImage共用的单图片请求 owner,负责 generation、取消、fallback、Reload、progress 和租约提交。AvatarToken:从全局 Token 派生 Avatar 与 AvatarGroup 共用的视觉值。AvatarGroup:子 Avatar 排列、重叠宽度、折叠计数和 Flyout 生命周期。
4. 状态与数据流
Source / FallbackSource / RequestOptions / Bounds / render scaling
-> ImageLoadController generation
-> Application.GetImageLoader().LoadAsync(...)
-> ImageContentValidator / SvgContentValidator (Application SVG conformance mode)
-> ImageLoadResult lease
-> UI dispatcher generation check
-> LoadedImage + LoadState/Error/Progress
-> ContentType(Image/Text/Icon) + pseudo-classes
-> AvatarTheme
AbstractAvatar 实现 IImageLoadControllerHost。它只向 controller 暴露当前 Visual、Normal priority、attach 状态、16 px
量化后的物理解码尺寸和状态提交回调,不直接访问 transport 或 cache。
SvgContentValidator 位于 Shared loader 管线中,在 codec 和 Avatar 状态提交之前执行。Application 构建时冻结默认
Compatible/可选 Strict 模式;Avatar 不把模式写入 ImageRequestOptions,也不根据来源类型选择另一条 SVG 校验路径。
Source、FallbackSource 或 RequestOptions 变化调用 SourceConfigurationChanged()。Arrange 完成后调用 RefreshSize();只有有效
Bounds 产生新的尺寸 bucket 时才需要新请求。属性快照和状态提交均在 UI 线程边界完成。
5. 组合结构模型
控件角色图
Avatar
-> Panel#RootLayout
-> PixelAlignedBorder#Frame (internal-observable)
-> IconPresenter#IconPresenter (internal-observable)
-> Border
-> Image#ImagePresenter (internal-observable)
-> Border
-> Viewbox (internal-observable)
-> TextBlock#PART_TextPresenter (template-stable)
AvatarGroup
-> child Avatar collection (public)
-> fold Avatar + Flyout (internal-observable)
协作节点
| 节点 | 类型 | 来源 | 生命周期 owner | 影响的 public API | 稳定性 | Agent 使用边界 |
|---|---|---|---|---|---|---|
Avatar |
public control | AvatarTheme.axaml |
visual tree | 单头像全部 API | public | 用户可直接使用 |
ImageLoadController |
internal coordinator | C# | AbstractAvatar |
Source、fallback、状态、Reload | internal-observable | 只用于理解统一状态机 |
Text Viewbox |
layout presenter | AvatarTheme.axaml |
visual tree | Text、Gap、Size、Font | internal-observable | 负责自然尺寸测量、向下缩放和居中,不作为独立 API 暴露 |
PART_TextPresenter |
TextBlock |
AvatarTheme.axaml |
template | Text、Font | template-stable | 提供自然排版尺寸;改名需同步实现、主题和文档 |
AvatarGroup |
public control | AvatarGroupTheme.axaml |
visual tree | Children、折叠和尺寸投影 | public | 用户可直接使用 |
| fold Avatar/Flyout | internal visual | C# + Theme | AvatarGroup |
MaxDisplayCount、trigger、fold colors | internal-observable | 不作为独立 API 暴露 |
内容选择只有三个结果:
LoadedImage != null时选择 Image。- 否则
Text != null时选择 Text。 - 否则选择 Icon。
因此 Loading、取消或主来源失败不会主动清空 Text/Icon。Controller 在主来源终态失败后至多请求一次 FallbackSource;主来源
和 fallback 身份相同则不重复请求。fallback 成功时设置 :loaded 与 :fallback,最终失败时设置 :failed。
事件与状态提交顺序必须一致:先保存当前结果和状态,再触发 ImageOpened;最终失败先提交错误状态,再触发 ImageFailed。
取消、detach 和 generation 过期不属于图片失败事件。
6. 生命周期与模板接入
成功的 ImageLoadResult 是图片使用租约。Controller 持有当前租约,来源变化、detach、最终失败或 controller dispose 时释放。
替换图片时先接管新租约,再释放旧租约,避免 decoded cache entry 在视觉切换前被销毁。
Reload() 不创建新的公开来源状态;它复制当前 options,并只把本次请求的
CacheRead 覆盖为 ImageCacheReadPolicy.RefreshSource:
- HTTP 允许使用已有 metadata 做条件重验证。
- File、Asset、StorageFile、Bytes 和 Stream 强制重新读取来源。
- Reload 期间可保留旧图片;新结果只有通过 generation 校验后才能替换。
new BorrowedImageSource(image) 是 borrowed source。其结果租约只约束消费期,不能 dispose 调用方的 IImage。
| 获取/建立 | 对应释放 |
|---|---|
| controller attach 与 loader waiter | visual detach 时 controller detach/cancel |
当前 ImageLoadResult lease |
Source 清空、替换终态、detach 或 dispose |
| AvatarGroup motion binding | group detach 时 dispose |
| AvatarGroup fold Flyout/临时视觉 | rebuild、detach 或 owner 释放时清理 |
Reattach 时 controller 使用当前 Source 和当前尺寸重新请求。文字布局不订阅 template part 事件;模板重套用后由 Border、Viewbox 和 TextBlock 的标准 measure/arrange 流程重新建立可用宽度、自然尺寸与缩放关系。AvatarGroup 只管理组合视觉,不取消或复用子 Avatar 的图片请求。
7. 交互与事件处理
Avatar 没有独立的 pointer 或 keyboard 状态机;基础交互由 Avalonia 控件和 Theme 处理。图片回调先在 UI dispatcher 校验
generation 和 attach 状态,再提交结果。成功时先保存 lease、状态和内容,再触发 ImageOpened;最终失败时先提交 error 状态,
再触发 ImageFailed。取消、detach 和过期 generation 不触发失败事件。Reload() 是唯一命令式图片入口。
8. 内部算法与关键流程
AvatarTheme.axaml 通过 internal ContentType 选择 Image、Text 或 Icon presenter。唯一稳定 template part 是
PART_TextPresenter: Avalonia.Controls.TextBlock。
AbstractAvatar 只把 Gap 投影为模板内部的水平 Text padding。Border 用该 padding 定义文字可用区域,Viewbox 以
PART_TextPresenter 的自然排版尺寸为输入并使用 StretchDirection=DownOnly:自然宽度不超过可用宽度时保持原尺寸,超过时
等比缩小到可用区域。缩放和居中由同一个模板布局坐标系负责,不单独调用文字测量工具,也不叠加横向补偿 transform;Text、
Font、Gap 或控件尺寸变化通过 Avalonia measure/arrange 自动重新计算。
Size 非 NaN 时保存原 SizeType 并切换到 Custom;恢复 NaN 后回到原 SizeType。Circle 形状把最终宽度的一半写入模板
CornerRadius。图片请求尺寸来自 Bounds 与 TopLevel render scaling,而不是 Token 名称或逻辑像素直接值。
9. 资源、性能与 AOT 边界
- 不进行同步网络或文件 I/O。
- 不使用固定延迟等待布局;Arrange 和尺寸 bucket 是唯一尺寸就绪信号。
- 文字适配使用无状态的 Border/Viewbox 布局,不重复测量字形,不持有 template part 事件订阅。
- 不创建控件私有
HttpClient、cache 或 scheduler。 - Theme、codec 和 loader 通过静态注册保留,不使用反射扫描。
- Browser 使用统一 loader 的 Browser 能力矩阵;Avatar 不增加平台分支 API。
- SVG 一致性模式是封闭的应用级 enum;Avatar 不持有 validator delegate,不增加反射或 NativeAOT 动态发现边界。
- 快速 Source 切换只允许当前 generation 回写,旧结果必须及时释放。
10. 维护不变量
ImageLoadController是 Avatar 与AsyncImage的单图状态机 owner,不在 Avatar 内复制请求协调逻辑。- 内容优先级始终为 Image、Text、Icon;Loading 和取消不清空可用降级内容。
- 每个已提交 cache image 必须由一个有效 lease 支撑;borrowed image 永不由控件销毁。
- Source、options、尺寸或 attach generation 变化后,旧结果只能释放,不能回写状态。
Compatible/Strict差异只能来自 SharedSvgContentValidator;Avatar 不按 Asset/File/HTTP 或单次请求改写该结论。- Template reapply、detach、group rebuild 和 Application dispose 都有明确的取消、解绑和释放路径。
- 文字的自然尺寸、可用宽度、缩放和居中必须由同一个模板布局路径完成;不得恢复独立文字测量或补偿平移。
- 单头像和 Token 只存在于
AtomUI.Controls,Desktop 包只拥有 AvatarGroup 组合能力。
11. 测试与验证
维护 Avatar 时至少覆盖:
- Source、FallbackSource、Text、Icon 优先级与状态伪类。
- A -> B 快速切换、Reload、detach/reattach、尺寸 bucket 和 render scaling。
- 主失败/fallback 成功、最终失败、事件顺序和 progress generation。
- owned cache image 与 borrowed image 的释放责任。
- AvatarTheme 独立于 Desktop 类型,AvatarGroup 仍能消费同一 Token。
- 短文本不放大;临界宽度与长文本缩小后保持水平居中,宽度不超过
Width - 2 * Gap;运行时 Gap 变化重新布局。 - Gallery 示例只使用
Source/ImageSource。 - 默认 Application
Compatible模式加载含重复 id 的本地 SVG;Strict下同一来源报告InvalidImageData,并继续验证 FallbackSource、ImageFailed、伪类和 generation 语义。两种模式对不安全或超预算 SVG 的结果完全相同。