惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
L
LINUX DO - 热门话题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
雷峰网
雷峰网
aimingoo的专栏
aimingoo的专栏
博客园_首页
MongoDB | Blog
MongoDB | Blog
V
V2EX
GbyAI
GbyAI
量子位
Microsoft Azure Blog
Microsoft Azure Blog
有赞技术团队
有赞技术团队
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
B
Blog
Microsoft Security Blog
Microsoft Security Blog
S
SegmentFault 最新的问题
O
OpenAI News
N
News and Events Feed by Topic
博客园 - Franky
爱范儿
爱范儿
Forbes - Security
Forbes - Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V2EX - 技术
V2EX - 技术
Application and Cybersecurity Blog
Application and Cybersecurity Blog
N
News and Events Feed by Topic
N
News | PayPal Newsroom
Schneier on Security
Schneier on Security
Cloudbric
Cloudbric
Security Archives - TechRepublic
Security Archives - TechRepublic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Recent Commits to openclaw:main
Recent Commits to openclaw:main
人人都是产品经理
人人都是产品经理
P
Privacy International News Feed
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog RSS Feed
阮一峰的网络日志
阮一峰的网络日志
D
DataBreaches.Net
Last Week in AI
Last Week in AI
罗磊的独立博客
Spread Privacy
Spread Privacy
Recent Announcements
Recent Announcements
The Cloudflare Blog
Google DeepMind News
Google DeepMind News
AWS News Blog
AWS News Blog
The Register - Security
The Register - Security
Y
Y Combinator Blog
J
Java Code Geeks
I
Intezer

博客园 - China Soft

.NET 程序保护实战系列01-流水线架构与保护引擎总览 试官问:你用 AI 编程半年了,那怎么保证 Claude Code 写出来的代码是对的? C#实现控制台多区域输出 园友特惠| 1Panel 企业版 & AI 一体机限量放售,最高直降 5100! 上位机程序发布打包成安装包---Inno Setup 这是一篇学习笔记,分享给大家,如果大家喜欢,也会开启USB的专辑 我问了 AI 一个问题:编码能力贬值后,什么能力值钱? Visual Studio 2026(VS2026) 密钥/激活码 Dddify:给 ASP.NET Core 项目一套轻量、清晰、可落地的 DDD 基础设施 C# ESP32/STM32 轻量 Web 能力库:PicoServer.Nano 高性能百度OCR ONNX Runtime C#实现 别再说 AI 开发就是调接口了!5 种主流模式一次讲清 使用Cursor实现管理系统登录界面的快速开发 傻子可懂的 Harness Engineering 入门教程 + 项目实战,一次搞懂 AI 编程工程化! 提升 Text2SQL 准确率 AI/Vibe Coding,本质是软件人工时代向软件工业时代发展 体验优先:十分钟使用 Python+LangChain 玩转阿里通义千问 大模型基础(二):必懂5大基础概念《Token、上下文窗口、Embedding、预训练、微调》 大模型基础(一):什么是LLM? Prompt、Agent、Function Call、Skill、MCP,傻傻分不清楚? 那些喊着AI 要淘汰你的人,正在靠你的焦虑赚大钱! 作为一线研发,我快被全网的 AI 焦虑逼疯了。 Qt QtWebEngine 白屏的解决方案 都是微软亲儿子,WPF凭啥干不掉WinForm?这3个场景说明白了 .NET 高级开发 | C# 中的动态代码:反射、EMIT、表达式树、Roslyn、Source Generators 大模型看大模型:推理Token的能耗用电量比对 WorkBuddy:从“我是谁”到“帮我干活” .NET 高级开发 | 开发 .NET 诊断工具、链路追踪原理 Prompt 焚诀——一个模板,终结你和 AI 的所有沟通问题 [项目]涉案资产信息化管理平台(山东某执法单位定制开发) 大模型RAG实战,从被骂不靠谱到成为部门MVP,这是我的踩坑全记录 白帽子为什么几乎都绕不开 httpx:一款 HTTP 资产探测工具的技术价值 从Prompt工程到Skill工程:Agent Skills开放标准彻底改变了AI协作方式 MAUI项目在Android平台通过U盘实现软件更新 微软竟然出了免费的 AI 应用开发课?!我已经学上了 一文彻底搞懂 OpenClaw 的架构设计与运行原理(万字图文) AI到底聪明在哪——从手机人脸识别说起 硬核科普:为何小米任天堂封杀 Root?揭秘操作系统从启动到“夺权”的底层游戏
在 Avalonia 中编写高性能动画
China Soft · 2026-05-12 · via 博客园 - China Soft

https://www.cnblogs.com/BettaFish/p/20012355

在 Avalonia 框架中,我们通常使用 XAML 编写动画。然而,实际上 Avalonia 中还存在另一套鲜为人知(?)的动画系统——Composition Animation(合成动画)

本文将介绍如何在 Avalonia 中编写合成动画,以及它相比普通 XAML 动画的优点

XAML 动画

在介绍合成动画之前,我们先复习一下目前主要使用的 XAML 动画的写法:

<Border
Name="border1"
Width="200"
Height="200"
Background="Red">
<Border.Styles>
<Style>
<Style.Animations>
<Animation PlaybackDirection="Alternate" IterationCount="10" Duration="0:0:3">
<KeyFrame Cue="0%">
<Setter Property="TranslateTransform.X" Value="0.0" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="TranslateTransform.X" Value="1000.0" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
</Border>

这段代码为 Border 创建了一个动画,让它沿着 X 方向往返移动 10 次

简单易读!然而,这种动画有一个重大的缺陷:它运行在 UI 线程上,因此 UI 线程的高负载操作(如 Measure / Arrange)会严重影响动画的流畅程度。反过来,这些动画也会影响 UI 线程上的输入事件等,给用户体验造成很大影响。如果我们调用 Thread.Sleep() 模拟 UI 线程卡顿,动画将会完全停止

1

而且,Transition 系统也基于相同的原理。因此,在 Avalonia 中你用 XAML 编写的所有动画,理论上都有上面的缺点!

合成动画

为了解决这些问题,Avalonia 11 引入了全新的合成(Composition)渲染器,也带来了这篇文章的主角——合成动画(Composition Animation)系统

合成动画不是什么新鲜事物,它最早出现在 UWP / WinUI 中。可惜无论是 WinUI 还是 Avalonia,关于合成动画的资料都少之又少,只能通过硬啃 Avalonia 的源码来略知一二

我们需要知道的是,每个 Avalonia 控件在 UI 线程有一个 CompositionVisual (继承自 CompositionObject),同时在渲染层都有一个对应的 ServerCompositionVisual(继承自 ServerObject)。CompositionObject 上的每个属性在 ServerObject 上都有对应的属性

修改 XAML 控件上的属性时,会先修改 UI 线程的 CompositionVisual 上的相应属性,再序列化到渲染线程的 ServerCompositionVisual。在渲染过程中,合成器会根据 ServerCompositionVisual 上的属性调整渲染指令参数,再将这些指令发送到各个后端进行最终渲染

UI 线程

渲染线程

序列化

XAML 控件

CompositionVisual

ServerCompositionVisual

渲染后端

Skia

Vulkan

...

XAML 动画通过对 XAML 控件上的属性进行插值,而合成动画在渲染层计算并直接修改 ServerObject 上的属性,从而摆脱对 UI 线程的依赖

在 Avalonia 中,我们可以用以下方式定义并使用一个关键帧合成动画:

private void AnimateSquare(Compositor compositor, CompositionVisual redVisual)
{
Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation();
animation.InsertKeyFrame(1f, new Vector3(1000f, 0f, 0f));
animation.Duration = TimeSpan.FromSeconds(2);
animation.Direction = PlaybackDirection.Alternate;
animation.IterationCount = 10;
redVisual.StartAnimation("Translation", animation);
}
var visual = ElementComposition.GetElementVisual(border1);
AnimateSquare(visual!.Compositor, visual);

现在再来调用 Thread.Sleep() ,你可以发现合成动画依然能正常运行,完全不受 UI 线程影响!

2

同样,也可以编写隐式动画,在某个属性变化时自动触发:

private ImplicitAnimationCollection? _implicitAnimations;
 
private void EnsureImplicitAnimations()
{
var compositor = ElementComposition.GetElementVisual(this)!.Compositor;
 
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
offsetAnimation.Target = "Offset";
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
offsetAnimation.Duration = TimeSpan.FromMilliseconds(400);
 
var rotationAnimation = compositor.CreateScalarKeyFrameAnimation();
rotationAnimation.Target = "RotationAngle";
rotationAnimation.InsertKeyFrame(.5f, 0.160f);
rotationAnimation.InsertKeyFrame(1f, 0f);
rotationAnimation.Duration = TimeSpan.FromMilliseconds(400);
 
var animationGroup = compositor.CreateAnimationGroup();
animationGroup.Add(offsetAnimation);
animationGroup.Add(rotationAnimation);
 
_implicitAnimations = compositor.CreateImplicitAnimationCollection();
_implicitAnimations["Offset"] = animationGroup;
}
// 元素加载完成后调用
compositionVisual.ImplicitAnimations = _implicitAnimations;

3

然而,合成动画的强大之处远不止于此。

注意到刚刚隐式动画里的 InsertExpressionKeyFrame 了吗?这里的字符串不是硬编码的关键字,而是一种用特定语法编写的表达式,有一个专门的 解释器 负责这些表达式的解析与执行。

在 Avalonia 中,表达式的语法和 WinUI 里的几乎完全相同,可以直接参考 WinUI 文档 。表达式动画非常重要的一个功能就是它可以设置引用,即“监听”其他 CompositionObject 上属性的变化并在属性变化时重新对表达式进行计算。

例如,我们就可以基于 ExpressionAnimation 来模拟一堆齿轮(完整代码见 Avalonia.Labs/samples/Avalonia.Labs.Catalog/Views/Composition/Gears.axaml.cs ):

private void ConfigureGearAnimation(CompositionVisual currentGear, CompositionVisual previousGear)
{
var compositor = currentGear.Compositor;
var rotationExpression = compositor.CreateExpressionAnimation("-prevGear.RotationAngle");
rotationExpression.SetReferenceParameter("prevGear", previousGear);
currentGear.StartAnimation("RotationAngle", rotationExpression);
}

只需要对第一个齿轮播放动画,后面所有齿轮都会根据前一个齿轮的运动状态旋转。即使有上千个齿轮,动画也能保持较高帧率运行

4

如果觉得字符串表达式写着很恶心,Avalonia.Labs 中的 ExpressionBuilder 能允许你用 C# 代码辅助编写这些表达式

例如,上面的代码使用 ExpressionBuilder 后就可以变成这样:

var rotateExpression = - previousGear.GetReference().RotationAngle;
currentGear.StartAnimation("RotationAngle", rotateExpression);

当然,合成动画也有一定的局限性。由于在渲染线程运行,我们在 UI 线程无法直接获取到合成动画的进度,UI 线程的 Visual 的属性值和最终渲染所使用的属性值也是不同的

var compositionVisual = ElementComposition.GetElementVisual(border1)!;
var animation = compositor.CreateDoubleKeyFrameAnimation();
animation.Duration = TimeSpan.FromSeconds(4);
animation.InsertKeyFrame(0f, 1f);
animation.InsertKeyFrame(1f, 0f);
compositionVisual.StartAnimation("Opacity", animation);
await Task.Delay(2000);
var xamlOpacity = border1.Opacity;
var visualOpacity = compositionVisul.Opacity;

一个解决方案是维护一个定时器,通过运行时间估算出当前动画进度和值,但仍不能确保与实际渲染的值的完全相同

如何选择?

尽管我个人建议尽可能使用合成动画。然而,合成动画的一个巨大缺陷就是他不适合在 Style 中使用,且编写合成动画所需的代码量远远大于 XAML 动画。因此,目前推荐将合成动画用于以下场景

  • 性能敏感
  • 动画数量较多
  • 需要持续播放
  • 多个动画目标互相联动(使用 ExpressionAnimation)

目前已经有 PR 实现了在 XAML 中编写隐式合成动画,但尚未合并

官方文档中也记载了一种方法(和 PR 中的类似),使用 AttachedProperty 来间接实现在 XAML 中编写合成动画

一旦有官方 API 能够在 XAML 中编写合成动画,你应该始终选择它而非传统动画