在上一个AnimationPlayer例子上进行扩展,让其具备完整的小型动画功能。
AnimationPlayer类
public partial class AnimationPlayer : ObservableObject
{
// --------------------- 内部类 ---------------------
public class TimedAction
{
public double StartTime { get; set; }
public double? EndTime { get; set; }
public bool OneTime { get; set; }
private bool executed = false;
private double fps;
internal Action<double>? localAction;
internal Action<double>? globalAction;
internal Action<double, double>? dualAction;
public TimedAction(double startTime, double? endTime = null)
{
StartTime = startTime;
EndTime = endTime;
}
public TimedAction Once()
{
OneTime = true;
return this;
}
public TimedAction Fps(double fps)
{
this.fps = fps;
return this;
}
public TimedAction PlayLocal(Action<double> action)
{
localAction = action;
return this;
}
public TimedAction PlayGlobal(Action<double> action)
{
globalAction = action;
return this;
}
public TimedAction PlayDual(Action<double, double> action)
{
dualAction = action;
return this;
}
public bool IsActive(double elapsedSeconds)
{
return elapsedSeconds >= StartTime &&
(EndTime == null || elapsedSeconds <= EndTime.Value + 1 / fps);
}
public void TryExecute(double elapsedSeconds, double globalProgress, double totalDuration)
{
if (OneTime && elapsedSeconds < StartTime)
{
executed = false;
}
if (!IsActive(elapsedSeconds)) return;
double actionEnd = EndTime ?? totalDuration;
double localProgress = (elapsedSeconds - StartTime) / (actionEnd - StartTime);
localProgress = Math.Clamp(localProgress, 0, 1);
if (OneTime && executed) return;
localAction?.Invoke(localProgress);
globalAction?.Invoke(globalProgress);
dualAction?.Invoke(localProgress, globalProgress);
if (OneTime)
executed = true;
}
public void Reset() => executed = false;
}
// --------------------- 字段 ---------------------
private readonly DispatcherTimer timer = new DispatcherTimer();
private readonly Stopwatch stopwatch = new Stopwatch();
private TimeSpan elapsedOffset = TimeSpan.Zero; // 累计暂停/Seek时间
private bool isRunning;
// --------------------- 属性 ---------------------
[ObservableProperty] private double _speed = 1.0;
[ObservableProperty] private double _duration = 10.0;
[ObservableProperty] private double _progress;
[ObservableProperty] private string _timeText = "[[ stopped ]]";
[ObservableProperty] private bool _canPause = false;
[ObservableProperty] private bool _canResume = false;
[ObservableProperty] private bool _canStop = false;
[ObservableProperty] private bool _canSeek = false;
[ObservableProperty] private double _fps = 0;
private List<TimedAction> Actions { get; } = new();
public event Action? AnimationCompleted;
public AnimationPlayer()
{
Fps = 60;
timer.Tick += (_, __) => UpdateProgress();
}
partial void OnFpsChanged(double value)
{
timer.Interval = TimeSpan.FromMilliseconds(1000 / value);
foreach (var action in Actions)
action.Fps(value);
}
// --------------------- 链式添加动作 ---------------------
public TimedAction At(double startTime, double? endTime = null)
{
var action = new TimedAction(startTime, endTime).Fps(Fps);
Actions.Add(action);
return action;
}
// --------------------- 控制方法 ---------------------
public void Start()
{
stopwatch.Restart();
elapsedOffset = TimeSpan.Zero;
isRunning = true;
foreach (var action in Actions)
action.Reset();
timer.Start();
UpdateStates();
}
public void Pause()
{
if (!CanPause) return;
stopwatch.Stop();
elapsedOffset += stopwatch.Elapsed;
isRunning = false;
timer.Stop();
UpdateStates();
}
public void Resume()
{
if (!CanResume) return;
stopwatch.Restart();
isRunning = true;
timer.Start();
UpdateStates();
}
public void Stop()
{
if (!CanStop) return;
isRunning = false;
timer.Stop();
stopwatch.Reset();
elapsedOffset = TimeSpan.Zero;
Progress = 0;
TimeText = "[[ stopped ]]";
foreach (var action in Actions)
action.Reset();
UpdateStates();
}
public void Seek(double seconds)
{
if (!CanSeek) return;
seconds = Math.Clamp(seconds, 0, Duration);
elapsedOffset = TimeSpan.FromSeconds(seconds / Speed);
stopwatch.Restart();
UpdateProgress();
}
// --------------------- 更新方法 ---------------------
private void UpdateProgress()
{
double elapsedSeconds = (stopwatch.Elapsed + elapsedOffset).TotalSeconds * Speed;
if (elapsedSeconds >= Duration)
{
elapsedSeconds = Duration;
isRunning = false;
timer.Stop();
AnimationCompleted?.Invoke();
}
Progress = Math.Clamp(elapsedSeconds / Duration, 0, 1);
TimeText = TimeSpan.FromSeconds(elapsedSeconds).ToString(@"hh\:mm\:ss\.fff");
foreach (var timedAction in Actions)
timedAction.TryExecute(elapsedSeconds, Progress, Duration);
UpdateStates();
}
// --------------------- 状态更新 ---------------------
private void UpdateStates()
{
CanPause = isRunning;
CanResume = !isRunning && Progress > 0 && Progress < 1;
CanStop = isRunning || (Progress > 0 && Progress < 1);
CanSeek = Progress > 0 && Progress < 1;
}
}
ImageWipe.axaml代码
<Window 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" Height="250" Width="300" x:Class="AvaloniaUI.ImageWipe" Title="ImageWipe"> <Grid RowDefinitions="auto,auto"> <Grid> <Image Source="avares://AvaloniaUI/Resources/Images/night.jpg"/> <Image Source="avares://AvaloniaUI/Resources/Images/day.jpg" Name="imgDay"/> </Grid> <Button Grid.Row="1" Content="Start" HorizontalAlignment="Center" Click="Button_Click"/> </Grid> </Window>
ImageWipe.axaml.cs代码
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Media;
using Shares.Avalonia;
using System.Net;
namespace AvaloniaUI;
public partial class ImageWipe : Window
{
private readonly AnimationPlayer player = new AnimationPlayer() { Duration = 5 };
private GradientStop transparentStop = new GradientStop() { Color = Colors.Transparent };
private GradientStop visibleStop = new GradientStop() { Color = Colors.Black };
public ImageWipe()
{
InitializeComponent();
player.At(0).PlayLocal(p =>
{
transparentStop.Offset = p;
visibleStop.Offset = p + 0.2;
imgDay.OpacityMask = new LinearGradientBrush
{
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 0, RelativeUnit.Relative),
GradientStops = new GradientStops { transparentStop, visibleStop }
};
});
}
private void Button_Click(object? sender, RoutedEventArgs e)
{
player.Start();
}
}
运行效果

浙公网安备 33010602011771号