实现粒子效果,继承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号