值得注意的是在axaml中使用自定义动画器时,需要指定动画器的类型 可以略微做一些改进支持在字符串之间进行插值动画。动画过程中会根据进度逐渐显示新字符串的内容同时保留旧字符串的公共前缀部分。public class StringAnimator : InterpolatingAnimatorstring { /// summary /// 计算插值结果。 /// /summary /// param nameprogress进度0到1之间的值指在两个关键帧之间过渡的时间位置。/param /// param nameoldValue旧值动画开始时第一个关键帧的值。/param /// param namenewValue新值动画结束时最后一个关键帧的值。/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.RegisterCustomAnimatorstring, StringAnimator(); } }使用方法跟官方示例一样。自定义离散动画如果要实现通用的离散动画该怎么实现呢跟上面类似我们可以从InterpolatingAnimatorT继承一个DiscreteAnimatorT类代码很简单放弃中间的过渡部分直接在进度达到100%时切换到新值代码如下public class DiscreteAnimatorT : InterpolatingAnimatorT { public override T Interpolate(double progress, T oldValue, T newValue) { return progress 1 ? oldValue : newValue; } static DiscreteAnimator() { // 注册动画器 Animation.RegisterCustomAnimatorT, DiscreteAnimatorT(); } }使用方法跟上面一样TextBlock Text离散动画器文本切换几种不同的样式。 TextBlock.Styles Style SelectorTextBlock Style.Animations Animation Duration0:0:3 IterationCountInfinite PlaybackDirectionAlternate KeyFrame Cue0% Setter PropertyFontStyle ValueNormal Animation.Animator app:DiscreteAnimator x:TypeArgumentsFontStyle / /Animation.Animator /Setter /KeyFrame KeyFrame Cue30% Setter PropertyFontStyle ValueItalic Animation.Animator app:DiscreteAnimator x:TypeArgumentsFontStyle / /Animation.Animator /Setter /KeyFrame KeyFrame Cue60% Setter PropertyFontStyle ValueOblique Animation.Animator app:DiscreteAnimator x:TypeArgumentsFontStyle / /Animation.Animator /Setter /KeyFrame /Animation /Style.Animations /Style /TextBlock.Styles /TextBlock因为我们使用了泛型在指定动画器时需要使用x:TypeArguments来指定类型参数app:DiscreteAnimator x:TypeArgumentsFontStyle /否则会报错。另一种实现自定义动画的变通方法如果不想实现通用的动画器我们可以注册AvaloniaProperty并在属性的PropertyChanged回调中直接修改属性值来实现动画效果。以下是一个简单的示例演示如何通过注册一个名为DiscreteValue的AvaloniaProperty来实现几何路径变换的动画//注册一个AvaloniaProperty类型为int默认值为0 public const string DISCRETE_VALUE DiscreteValue; public static readonly StyledPropertyint DiscreteValueProperty AvaloniaProperty.RegisterPath, 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]; }