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

推荐订阅源

Attack and Defense Labs
Attack and Defense Labs
The GitHub Blog
The GitHub Blog
C
Check Point Blog
博客园_首页
MongoDB | Blog
MongoDB | Blog
N
Netflix TechBlog - Medium
F
Full Disclosure
Microsoft Security Blog
Microsoft Security Blog
爱范儿
爱范儿
Recent Announcements
Recent Announcements
阮一峰的网络日志
阮一峰的网络日志
G
GRAHAM CLULEY
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Threat Research - Cisco Blogs
C
Cybersecurity and Infrastructure Security Agency CISA
V
Vulnerabilities – Threatpost
K
Kaspersky official blog
博客园 - 司徒正美
S
Schneier on Security
T
The Exploit Database - CXSecurity.com
Project Zero
Project Zero
云风的 BLOG
云风的 BLOG
Cisco Talos Blog
Cisco Talos Blog
Know Your Adversary
Know Your Adversary
雷峰网
雷峰网
V
V2EX - 技术
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Spread Privacy
Spread Privacy
罗磊的独立博客
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
S
Security Affairs
SecWiki News
SecWiki News
Schneier on Security
Schneier on Security
O
OpenAI News
Jina AI
Jina AI
PCI Perspectives
PCI Perspectives
Cyberwarzone
Cyberwarzone
Y
Y Combinator Blog
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog RSS Feed
I
InfoQ
D
Docker
P
Palo Alto Networks Blog
Recorded Future
Recorded Future
M
MIT News - Artificial intelligence
博客园 - Franky
B
Blog
Scott Helme
Scott Helme
博客园 - 叶小钗
D
DataBreaches.Net

博客园_首页

马斯克都在用的"第一性原理":为什么90%的程序员在"卷框架",而高手只看一件事? Jenkins-批量自动化构建指定目录或者视图下所有Job或者指定Job RAG夺命10连问,你能抗住第几问? 硬件连接器简介和图文大全 GPT-5.5 开启更强的智能体工作方式 零代码 AI 自动化测试神器!Browser‑Use Web UI 保姆级教程,测试人直接上手 Netty保姆级全解析|技术背景+核心知识点+生产实战教程 【译】Bookmark Studio:在 Visual Studio 中实现书签功能升级 Tomcat的事件监听机制:观察者模式 .NET 11 Preview 3 发布:C# 15 union 类型终补齐,Kestrel 暴增 40% 生成器与迭代器 【Java安全】URLDNS利用链分析 高数学习笔记 一分钟学会用Markdown绘制Mermaid思维导图 - 天恩软件 利用surging 网络组件重构插件开发 踩坑记录:UTF-8、UTF-8-BOM 与 GB2312 读取的乱码真相 我把市面上 UI 自动化 Skill 全踩坑一遍后,自己写了个真正能用的 存储器类型汇总 PortSwigger SQL注入LAB3 《软件测试策略》——测试相关技术(编写 bug 报告)(二) ElasticSearch中的索引模板详解 一份CLAUDE.md,为何能让GitHub榜首项目狂揽6万星? 设计圈真的要变天了:ChatGPT Image 2 不只是会生图了 多租户系统框架的界面分析设计 SpringAI入门指南 我为何选择私有化客服系统作为独立开发的方向 RAG 是什么?16 种 RAG 方案一次讲清!AI 应用开发必学 | 万字干货 HackTheBox Cap 靶机:从 IDOR 到 PCAP 凭据提取再到 Capabilities 提权 Python批量图片拼接脚本:支持行列布局、最后一行居中、自然排序 使用 Java 提取 HTML 文件中的纯文本内容 AI开发-python-LangGraph框架(3-31-LangGraph 「合并式状态管理」的原理与实践) keycloak~实现OAuth 2.0 Token Exchange SeaTunnel + AI:一句“我要做什么”,能不能直接变成一份能跑的配置? 本体论的启示:从零开始,如何让AI“学会”使用计算器 【译】Visual Studio 三月更新 —— 打造专属自定义 Agent Tomcat组件管理源码详解 推荐一个开箱即用的.NET权限管理平台:Magic.NET .NET 调试器 netcoredbg 跨平台及其 LoongArch 架构支持进展 DualToken如何让模型理解自己画出来的东西? surging 的Agent插件研发全流程:从定义到落地 无硬件学LVGL—定时器篇:基于Web模拟器+MicroPython速通GUI开发 Kimi 新模型发布!教你如何在 Claude Code 上配置并使用最新的 k2.6 模型! UEFI Driver 程序框架 PREEMPT_RT补丁技术实现:RCU 零代码经验,我用Claude Code搓出的生产力工具 FastAPI订单防超卖实战:从数据库锁到Saga分布式事务,这一篇给你理清了 Kimi Code CLI 系统指令的摸索 以及 开发实战经验分享 智能运维2.0:从范式跃迁到落地实操——理论框架与实施指南 当 AIR 只支持 Mac,我开始重新思考操作系统这件事 深度学习开发笔记(一):跨平台纯C++训练和推理框架LibTorch介绍、开发环境搭建和Demo 必知必会:大模型训练通信开销计算详解与面试指南 AI开发-python-LangGraph框架(3-30-LangGraph 「追加式消息状态」的原理与实践) 新版本Dash完美支持原生FastAPI后端 如何写一个好的 skill 让你的效率加倍 如何灵活设置公式中各个部分的颜色? Claude Code命令速查大全 Netty入门|从BIO到Netty:一步步看懂Java网络编程的迭代逻辑 最小二乘问题详解21:稀疏GCP约束下的自由网平差与弱约束融合 SQL Server 性能优化实战(第一期):索引——查询加速的基石 API未授权访问,敏感信息正在裸奔 Agent 上下文窗口的有限与突破 Linux进阶--系统备份、恢复与可视化管理工具webmin、bt宝塔 AI 赋能微服务工程化:Surging Engine-CLI 的插件化 Agent 架构革新 [项目管理] 浅谈大数据项目的项目管理 一个零基础计算机学生决定开始写博客的原因 基于 OpenSpec 实现规范驱动开发 方差在扩散模型保护中的作用 别再写 if/else 了:让 LLM 自己决定调用哪个函数 Microsoft Agent Framework 创建智能体 Responses WebSocket 协议详解:为什么它会让 Agent 工作流更快 分享一个Oracle 性能诊断工具,代码已开源 深度学习进阶(十) RoI Align 华为云亮相 KubeCon EU 2026,共建“智能原生”基础设施,加速 Agentic AI 未来 FastAPI 生产环境避坑指南:用 Alembic 管理数据库迁移,别再手动改表结构了! AI开发-python-LangGraph框架(3-29-LangGraph 「覆盖式状态」的原理与实践) 开源项目PocoEmit.Mapper重构之扑风捉影 robotframework-aitester---向robotframework中引入大模型能力 Codex学习路线图 ElasticSearch中的分词器详解 Linux--Shell编程入门 FastAPI服务半夜又挂了?先别急着重启,查查你的数据库连接池“池子”是不是漏了 ElasticSearch集群数据备份恢复详解 MAF快速入门(24)整合多个Skill来源 电子小白:光耦到底是什么? 不用写脚本!Browser-Use 实操:AI 直接驱动浏览器自动化测试 【从0到1构建一个ClaudeAgent】协作-Worktree+任务隔离 SolonCode vs OpenCode 内存实测,差距高达 8 倍!(此战能封神吗?) 上周热点回顾(4.13-4.19) LLM核心参数配置指南:原理篇 关于数据库服务器资源降配的效能分析 这篇千万阅读的 AI 方法论,我三个月前已经在用了,效果有点离谱! [云原生] K8s 核心组件使用指南 基于深度学习的障碍物检测系统(YOLOv12完整代码+论文示例+多算法对比) Plist 二进制格式 高等数学-导数与微分(微分中值定理) Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 分享一下笔者的 Mac 装机必备软件 成人礼——2026.4.19 鲜花 深度学习进阶(九)池化技术的初步改进:RoI Pooling
Avalonia中的动画
Wuya · 2026-05-22 · via 博客园_首页

作者:王先荣
内置动画 在Avalonia中,常用的动画有以下几种:关键帧动画(KeyFrame Animation)、过渡动画(Transition Animation)和合成动画(Composition Animations)。它们在使用方式和适用场景有所不同,官方文档的总结如下表所示(https://docs.avaloniaui.net/docs/graphics-animation/animations)。

Type
Description
Use case
Keyframe Animations
 
Change one or more properties over a timeline with multiple keyframes.
 
Complex, multi-step animations triggered by style selectors.
 
Control Transitions
 
Animate a single property when its value changes.
 
Smooth visual feedback for property changes (opacity, color, size).
 
Composition Animations
 
Code-driven animations that run on the render thread.
 
Performance-sensitive or programmatic animations controlled from C#.

严格来说,它们都是插值动画(Interpolated Animations)。那么怎样才能实现离散动画呢?例如打字机效果的动画,游戏中的精灵动画。 Avalonia官方的自定义动画示例 Avalonia的官方示例项目(RenderDemo https://github.com/AvaloniaUI/Avalonia/tree/master/samples/RenderDemo)中,实现了一个名为CustomStringAnimator(https://github.com/AvaloniaUI/Avalonia/blob/master/samples/RenderDemo/Pages/CustomStringAnimator.cs)的动画类,它继承自InterpolatingAnimator<string>,并重写了Interpolate方法来实现打字机效果的动画。以下是该动画类的代码:

public class CustomStringAnimator : InterpolatingAnimator<string>
{
    public override string Interpolate(double progress, string oldValue, string newValue)
    {
        if (newValue.Length == 0) return "";
        var step = 1.0 / newValue.Length;
        var length = (int)(progress / step);
        var result = newValue.Substring(0, length + 1);
        return result;
    }
}

自定义动画

使用起来跟普通的动画几乎一样,需要指定Animator,代码如下(https://github.com/AvaloniaUI/Avalonia/blob/master/samples/RenderDemo/Pages/CustomAnimatorPage.xaml):

<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center">
  <TextBlock.Styles>
    <Style Selector="TextBlock">
      <Style.Animations>
        <Animation Duration="0:0:1" IterationCount="Infinite">
          <KeyFrame Cue="0%">
            <Setter Property="Text" Value="">
              <Animation.Animator>
                <pages:CustomStringAnimator/>
              </Animation.Animator>
            </Setter>
          </KeyFrame>
          <KeyFrame Cue="100%">
            <Setter Property="Text" Value="0123456789" >
              <Animation.Animator>
                <pages:CustomStringAnimator/>
              </Animation.Animator>
            </Setter>
          </KeyFrame>
        </Animation>
      </Style.Animations>
    </Style>
  </TextBlock.Styles>
</TextBlock>

View Code

值得注意的是在axaml中使用自定义动画器时,需要指定动画器的类型(<Animation.Animator><pages:CustomStringAnimator/></Animation.Animator>),否则会报错。 改进的字符串动画 在官方示例的基础上,我们可以略微做一些改进:支持在字符串之间进行插值动画。动画过程中会根据进度逐渐显示新字符串的内容,同时保留旧字符串的公共前缀部分。

public class StringAnimator : InterpolatingAnimator<string>
{
    /// <summary>
    /// 计算插值结果。
    /// </summary>
    /// <param name="progress">进度:0到1之间的值,指在两个关键帧之间过渡的时间位置。</param>
    /// <param name="oldValue">旧值:动画开始时,第一个关键帧的值。</param>
    /// <param name="newValue">新值:动画结束时,最后一个关键帧的值。</param>
    /// <returns>返回插值结果。</returns>
    public override string Interpolate(double progress, string oldValue, string newValue)
    {
        if (progress <= 0)
            return oldValue;
        if (progress >= 1)
            return newValue;
        int oldLength = string.IsNullOrEmpty(oldValue) ? 0 : oldValue.Length;
        int newLength = string.IsNullOrEmpty(newValue) ? 0 : newValue.Length;
        if (oldLength == 0 && newLength == 0)
            return string.Empty;
        if (oldLength == newLength && oldValue == newValue)
            return newValue;
        if (oldLength == 0 || newValue.StartsWith(oldValue))
        {
            // 如果旧值为空或新值以旧值开头,则从旧值开始插入新值的剩余部分
            return newValue.Substring(0, (int)(oldLength + (newLength - oldLength) * progress));
        }
        else if (newLength == 0 || oldValue.StartsWith(newValue))
        {
            // 如果新值为空或旧值以新值开头,则从新值开始删除旧值的剩余部分
            return oldValue.Substring(0, (int)(newLength + (oldLength - newLength) * (1 - progress)));
        }
        else
        {
            //如果没有包含关系,忽略旧值,直接从新值开始插入
            oldLength = 0;
            return newValue.Substring(0, (int)(oldLength + (newLength - oldLength) * progress));
        }
    }

    static StringAnimator()
    {
        // 注册动画器
        Animation.RegisterCustomAnimator<string, StringAnimator>();
    }
}

使用方法跟官方示例一样。 自定义离散动画 如果要实现通用的离散动画,该怎么实现呢?跟上面类似,我们可以从InterpolatingAnimator<T>继承一个DiscreteAnimator<T>类,代码很简单,放弃中间的过渡部分,直接在进度达到100%时切换到新值,代码如下:

public class DiscreteAnimator<T> : InterpolatingAnimator<T>
{
    public override T Interpolate(double progress, T oldValue, T newValue)
    {
        return progress < 1 ? oldValue : newValue;
    }

    static DiscreteAnimator()
    {
        // 注册动画器
        Animation.RegisterCustomAnimator<T, DiscreteAnimator<T>>();
    }
}

使用方法跟上面一样:

<TextBlock Text="离散动画器文本,切换几种不同的样式。">
    <TextBlock.Styles>
        <Style Selector="TextBlock">
          <Style.Animations>
            <Animation Duration="0:0:3" IterationCount="Infinite" PlaybackDirection="Alternate">
              <KeyFrame Cue="0%">
                <Setter Property="FontStyle" Value="Normal">
                    <Animation.Animator>
                        <app:DiscreteAnimator x:TypeArguments="FontStyle" />
                    </Animation.Animator>
                </Setter>
              </KeyFrame>
              <KeyFrame Cue="30%">
                <Setter Property="FontStyle" Value="Italic">
                    <Animation.Animator>
                        <app:DiscreteAnimator x:TypeArguments="FontStyle" />
                    </Animation.Animator>
                </Setter>
              </KeyFrame>
              <KeyFrame Cue="60%">
                <Setter Property="FontStyle" Value="Oblique">
                    <Animation.Animator>
                        <app:DiscreteAnimator x:TypeArguments="FontStyle" />
                    </Animation.Animator>
                </Setter>
              </KeyFrame>
            </Animation>
          </Style.Animations>
        </Style>
      </TextBlock.Styles>
</TextBlock>

因为我们使用了泛型,在指定动画器时需要使用x:TypeArguments来指定类型参数(<app:DiscreteAnimator x:TypeArguments="FontStyle" />),否则会报错。 另一种实现自定义动画的变通方法 如果不想实现通用的动画器,我们可以注册AvaloniaProperty,并在属性的PropertyChanged回调中直接修改属性值来实现动画效果。以下是一个简单的示例,演示如何通过注册一个名为DiscreteValue的AvaloniaProperty来实现几何路径变换的动画:

//注册一个AvaloniaProperty,类型为int,默认值为0
public const string DISCRETE_VALUE = "DiscreteValue";
public static readonly StyledProperty<int> DiscreteValueProperty =
    AvaloniaProperty.Register<Path, int>(DISCRETE_VALUE, 0);
//定义一个数组,存储不同的几何路径,在动画过程中根据DiscreteValue的值来切换不同的路径
private PathGeometry[] bells = [];      //这里省略了路径的定义
//创建一个动画并运行它
private void PlayAnimation_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
    var animation = new Animation()
    {
        Duration = TimeSpan.FromSeconds(8),
        IterationCount = new IterationCount(2),
        PlaybackDirection = PlaybackDirection.Alternate,
        Children =
        {
            new KeyFrame()
            {
                Setters = { new Setter(DiscreteValueProperty, 0), },
                KeyTime = TimeSpan.FromSeconds(0)
            },
            new KeyFrame()
            {
                Setters = { new Setter(DiscreteValueProperty, 1), },
                KeyTime = TimeSpan.FromSeconds(2)
            },
            new KeyFrame()
            {
                Setters = { new Setter(DiscreteValueProperty, 2), },
                KeyTime = TimeSpan.FromSeconds(4)
            },
            new KeyFrame()
            {
                Setters = { new Setter(DiscreteValueProperty, 3), },
                KeyTime = TimeSpan.FromSeconds(6)
            },
            new KeyFrame()
            {
                Setters = { new Setter(DiscreteValueProperty, 3), },
                KeyTime = TimeSpan.FromSeconds(8)
            }
        }
    };
    _ = animation.RunAsync(Path1);
}
//在属性的PropertyChanged事件中,根据DiscreteValue的值来切换不同的路径
private void Path1_PropertyChanged(object? sender, Avalonia.AvaloniaPropertyChangedEventArgs e)
{
    if (e.Property.Name == DiscreteValueProperty.Name)
    {
        Debug.WriteLine($"time: {DateTime.Now}, Path1 property changed: {e.Property.Name}, old value: {e.OldValue}, new value: {e.NewValue}");
        int value = (int)e.NewValue;
        Path1.Data = bells[value % bells.Length];
    }
}

这种方法只能在代码中调用,无法在axaml中使用,但它不需要实现通用的动画器,适合一些特定的动画效果。 本文相关代码可以在我的Gitee仓库中找到: https://gitee.com/xrwang2/avalonia-test/tree/master/AvaloniaMvvpDesktopApp