实现粒子效果,继承Decorator。
SonicEffect.cs
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using System;
using System.Collections.Generic;
namespace Shares.Avalonia.Effects;
// 点击触发的波纹特效装饰器
// 可包裹任意控件,在点击处绘制扩散的圆环波纹
public class SonicEffect : Decorator
{
    private readonly AnimationPlayer player = new AnimationPlayer();
    private readonly List<Ring> rings = new();
    private bool isActive;
    private Point clickPoint;
    public static readonly StyledProperty<double> RadiusProperty =
        AvaloniaProperty.Register<SonicEffect, double>(nameof(Radius), 60);
    public static readonly StyledProperty<double> StrokeThicknessProperty =
        AvaloniaProperty.Register<SonicEffect, double>(nameof(StrokeThickness), 5);
    public static readonly StyledProperty<Color> RippleColorProperty =
        AvaloniaProperty.Register<SonicEffect, Color>(nameof(RippleColor), Colors.Magenta);
    public static readonly StyledProperty<double> DurationSecondsProperty =
        AvaloniaProperty.Register<SonicEffect, double>(nameof(DurationSeconds), 1.2);
    public double Radius
    {
        get => GetValue(RadiusProperty);
        set => SetValue(RadiusProperty, value);
    }
    public double StrokeThickness
    {
        get => GetValue(StrokeThicknessProperty);
        set => SetValue(StrokeThicknessProperty, value);
    }
    public Color RippleColor
    {
        get => GetValue(RippleColorProperty);
        set => SetValue(RippleColorProperty, value);
    }
    public double DurationSeconds
    {
        get => GetValue(DurationSecondsProperty);
        set => SetValue(DurationSecondsProperty, value);
    }
    // 局部类型:表示单个波纹状态
    private record Ring(Point Center, double Radius, double Opacity);
    public SonicEffect()
    {
        AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
        // 点击触发波纹
        PointerPressed += OnPointerPressed;
        // 初始化动画播放器
        player.Fps = 60;
        player.Duration = DurationSeconds;
        player.At(0)
              .Ease("CubicEaseOut")
              .PlayLocal(OnFrame);
        // 动画结束时清理状态
        player.AnimationCompleted += () =>
        {
            rings.Clear();
            isActive = false;
            InvalidateVisual();
        };
        // 当 DurationSeconds 属性改变时,更新动画时长
        this.GetObservable(DurationSecondsProperty).Subscribe(value =>
        {
            player.Duration = value;
        });
    }
    // 点击时触发波纹动画
    private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
    {
        clickPoint = e.GetPosition(this);
        rings.Clear();
        rings.Add(new Ring(clickPoint, 0, 1));
        isActive = true;
        player.Stop();
        player.Start();
    }
    // 每帧更新波纹状态
    private void OnFrame(double localProgress)
    {
        if (!isActive || rings.Count == 0)
            return;
        double eased = localProgress;
        double radius = Radius * eased;
        double opacity = 1 - eased;
        rings[0] = rings[0] with { Radius = radius, Opacity = opacity };
        InvalidateVisual();
    }
    // 在控件上绘制波纹
    public override void Render(DrawingContext context)
    {
        base.Render(context);
        if (!isActive || rings.Count == 0)
            return;
        var ring = rings[0];
        // 计算颜色透明度
        byte a = (byte)Math.Clamp((int)(RippleColor.A * ring.Opacity), 0, 255);
        var color = Color.FromArgb(a, RippleColor.R, RippleColor.G, RippleColor.B);
        var brush = new SolidColorBrush(color);
        var pen = new Pen(brush, StrokeThickness);
        context.DrawEllipse(null, pen, ring.Center, ring.Radius, ring.Radius);
    }
}
FireworkEffect.cs
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media;
using System;
using System.Collections.Generic;
namespace Shares.Avalonia.Effects;
// 点击触发的烟花爆炸特效
// 播放1.2秒扩散 + 淡出动画
public class FireworkEffect : Decorator
{
    private readonly List<Particle> particles = new();
    private readonly Random random = new();
    private readonly AnimationPlayer player = new AnimationPlayer();
    private bool isActive;
    private double progress;
    public static readonly StyledProperty<double> RadiusProperty =
        AvaloniaProperty.Register<FireworkEffect, double>(nameof(Radius), 15.0);
    public static readonly StyledProperty<double> RadiusVariationProperty =
        AvaloniaProperty.Register<FireworkEffect, double>(nameof(RadiusVariation), 5.0);
    public static readonly StyledProperty<Color> StartColorProperty =
        AvaloniaProperty.Register<FireworkEffect, Color>(nameof(StartColor), Colors.DarkOrange);
    public static readonly StyledProperty<Color> EndColorProperty =
        AvaloniaProperty.Register<FireworkEffect, Color>(nameof(EndColor), Colors.LightYellow);
    public static readonly StyledProperty<int> BurstSizeProperty =
        AvaloniaProperty.Register<FireworkEffect, int>(nameof(BurstSize), 60);
    public static readonly StyledProperty<double> DurationSecondsProperty =
        AvaloniaProperty.Register<FireworkEffect, double>(nameof(DurationSeconds), 1.2);
    public double Radius
    {
        get => GetValue(RadiusProperty);
        set => SetValue(RadiusProperty, value);
    }
    public double RadiusVariation
    {
        get => GetValue(RadiusVariationProperty);
        set => SetValue(RadiusVariationProperty, value);
    }
    public Color StartColor
    {
        get => GetValue(StartColorProperty);
        set => SetValue(StartColorProperty, value);
    }
    public Color EndColor
    {
        get => GetValue(EndColorProperty);
        set => SetValue(EndColorProperty, value);
    }
    public int BurstSize
    {
        get => GetValue(BurstSizeProperty);
        set => SetValue(BurstSizeProperty, value);
    }
    public double DurationSeconds
    {
        get => GetValue(DurationSecondsProperty);
        set => SetValue(DurationSecondsProperty, value);
    }
    // 内部粒子结构
    private record Particle(Point Location, Vector Velocity, Color Color, double Diameter, double Opacity);
    public FireworkEffect()
    {
        AddHandler(PointerPressedEvent, OnPointerPressed, RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
        // 初始化 AnimationPlayer
        player.Fps = 60;
        player.Duration = DurationSeconds;
        player.Loop = false;
        // 每帧更新调用 OnFrame
        player.At(0)
              .Ease("CubicEaseOut")
              .PlayLocal(OnFrame);
        // 动画结束清理粒子
        player.AnimationCompleted += () =>
        {
            particles.Clear();
            isActive = false;
            InvalidateVisual();
        };
        // 当时长属性变化时同步动画
        this.GetObservable(DurationSecondsProperty).Subscribe(v => player.Duration = v);
    }
    // 点击触发爆炸
    private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
    {
        var pt = e.GetPosition(this);
        CreateBurst(pt);
        progress = 0;
        isActive = true;
        player.Stop();
        player.Start();
    }
    // 创建随机粒子群
    private void CreateBurst(Point origin)
    {
        particles.Clear();
        for (int i = 0; i < BurstSize; i++)
        {
            double radius = Radius + (random.NextDouble() * 2 - 1) * RadiusVariation;
            double angle = random.NextDouble() * Math.PI * 2;
            double speed = random.NextDouble() * 250 + 80;
            Vector vel = new(Math.Cos(angle) * speed, Math.Sin(angle) * speed);
            var color = Interpolate(StartColor, EndColor, random.NextDouble());
            particles.Add(new Particle(origin, vel, color, radius * 2, 1));
        }
    }
    // 每帧更新回调
    private void OnFrame(double localProgress)
    {
        if (!isActive || particles.Count == 0)
            return;
        progress = localProgress; // 已缓动的进度
        InvalidateVisual();
    }
    // 绘制当前帧
    public override void Render(DrawingContext context)
    {
        base.Render(context);
        if (!isActive || particles.Count == 0)
            return;
        double t = progress;
        double fade = 1 - t;
        foreach (var p in particles)
        {
            // 爆炸阶段:立即从中心四射
            double explosionT = t;
            var pos = p.Location + p.Velocity * Math.Pow(explosionT, 0.7) * 0.15;
            var color = Color.FromArgb((byte)(p.Color.A * fade), p.Color.R, p.Color.G, p.Color.B);
            var brush = new RadialGradientBrush
            {
                Center = new RelativePoint(pos, RelativeUnit.Absolute),
                GradientOrigin = new RelativePoint(pos, RelativeUnit.Absolute),
                RadiusX = new RelativeScalar(p.Diameter / 2, RelativeUnit.Absolute),
                RadiusY = new RelativeScalar(p.Diameter / 2, RelativeUnit.Absolute),
                GradientStops = new GradientStops
                    {
                        new GradientStop(color, 0.0),
                        new GradientStop(Color.FromArgb(0, color.R, color.G, color.B), 1.0)
                    }
            };
            context.DrawEllipse(brush, null, pos, p.Diameter / 2, p.Diameter / 2);
        }
    }
    // 颜色插值函数
    private static Color Interpolate(Color a, Color b, double t)
    {
        return Color.FromArgb(
            (byte)(a.A + (b.A - a.A) * t),
            (byte)(a.R + (b.R - a.R) * t),
            (byte)(a.G + (b.G - a.G) * t),
            (byte)(a.B + (b.B - a.B) * t)
        );
    }
}
ParticleEffectExamples.axaml代码
<UserControl xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="AvaloniaUI.ParticleEffectExamples"> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Spacing="5"> <SonicEffect Radius="80" StrokeThickness="4"> <Button Width="120" Height="40" Content="点击波纹"/> </SonicEffect> <FireworkEffect BurstSize="80" Radius = "8"> <Button Content="点击放烟花" Width="120" Height="40"/> </FireworkEffect> </StackPanel> </UserControl>
ParticleEffectExamples.axaml.cs代码
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System;
namespace AvaloniaUI;
public partial class ParticleEffectExamples : UserControl
{
    public ParticleEffectExamples()
    {
        InitializeComponent();
    }
}
运行效果

 
                    
                     
                    
                 
                    
                 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号