在上一个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();
    }
}

运行效果

 

image

 

posted on 2025-09-24 13:21  dalgleish  阅读(11)  评论(0)    收藏  举报