X3

RedSky

导航

C#Animation

Animation
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1.DX
{
    #region EasingFunction
    public interface IEasingFunction
    {
        double Ease(double normalizedTime);
    }
    public class CustomerEasingFunction : IEasingFunction
    {
        private Func<double, double> _func;
        public double Ease(double normalizedTime) => _func.Invoke(normalizedTime);
        public CustomerEasingFunction(Func<double, double> func) {  _func = func; }
    }
    public class LinearEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => normalizedTime;
    }

    public class InSineEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Cos(normalizedTime * Math.PI / 2);
    }

    public class OutSineEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => Math.Sin(normalizedTime * Math.PI / 2);
    }

    public class InOutSineEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => -(Math.Cos(Math.PI * normalizedTime) - 1) / 2;
    }

    public class InQuadEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => normalizedTime * normalizedTime;
    }

    public class OutQuadEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - (1 - normalizedTime) * (1 - normalizedTime);
    }

    public class InOutQuadEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? 2 * normalizedTime * normalizedTime
                : 1 - Math.Pow(-2 * normalizedTime + 2, 2) / 2;
        }
    }

    public class InCubicEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => normalizedTime * normalizedTime * normalizedTime;
    }

    public class OutCubicEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Pow(1 - normalizedTime, 3);
    }

    public class InOutCubicEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? 4 * normalizedTime * normalizedTime * normalizedTime
                : 1 - Math.Pow(-2 * normalizedTime + 2, 3) / 2;
        }
    }

    public class InQuartEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => normalizedTime * normalizedTime * normalizedTime * normalizedTime;
    }

    public class OutQuartEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Pow(1 - normalizedTime, 4);
    }

    public class InOutQuartEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? 8 * normalizedTime * normalizedTime * normalizedTime * normalizedTime
                : 1 - Math.Pow(-2 * normalizedTime + 2, 4) / 2;
        }
    }

    public class InQuintEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => Math.Pow(normalizedTime, 5);
    }

    public class OutQuintEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Pow(1 - normalizedTime, 5);
    }

    public class InOutQuintEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? 16 * normalizedTime * normalizedTime * normalizedTime * normalizedTime * normalizedTime
                : 1 - Math.Pow(-2 * normalizedTime + 2, 5) / 2;
        }
    }

    public class InExpoEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) =>
            normalizedTime == 0 ? 0 : Math.Pow(2, 10 * normalizedTime - 10);
    }

    public class OutExpoEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) =>
            normalizedTime == 1 ? 1 : 1 - Math.Pow(2, -10 * normalizedTime);
    }

    public class InOutExpoEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            if (normalizedTime == 0) return 0;
            if (normalizedTime == 1) return 1;
            return normalizedTime < 0.5
                ? Math.Pow(2, 20 * normalizedTime - 10) / 2
                : (2 - Math.Pow(2, -20 * normalizedTime + 10)) / 2;
        }
    }

    public class InCircEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => 1 - Math.Sqrt(1 - Math.Pow(normalizedTime, 2));
    }

    public class OutCircEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime) => Math.Sqrt(1 - Math.Pow(normalizedTime - 1, 2));
    }

    public class InOutCircEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? (1 - Math.Sqrt(1 - Math.Pow(2 * normalizedTime, 2))) / 2
                : (Math.Sqrt(1 - Math.Pow(-2 * normalizedTime + 2, 2)) + 1) / 2;
        }
    }

    public class InBackEasingFunction : IEasingFunction
    {
        private const double c1 = 1.70158;
        private const double c3 = c1 + 1;

        public double Ease(double normalizedTime) =>
            c3 * normalizedTime * normalizedTime * normalizedTime - c1 * normalizedTime * normalizedTime;
    }

    public class OutBackEasingFunction : IEasingFunction
    {
        private const double c1 = 1.70158;
        private const double c3 = c1 + 1;

        public double Ease(double normalizedTime) =>
            1 + c3 * Math.Pow(normalizedTime - 1, 3) + c1 * Math.Pow(normalizedTime - 1, 2);
    }

    public class InOutBackEasingFunction : IEasingFunction
    {
        private const double c1 = 1.70158;
        private const double c2 = c1 * 1.525;

        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? (Math.Pow(2 * normalizedTime, 2) * ((c2 + 1) * 2 * normalizedTime - c2)) / 2
                : (Math.Pow(2 * normalizedTime - 2, 2) * ((c2 + 1) * (2 * normalizedTime - 2) + c2) + 2) / 2;
        }
    }

    public class InElasticEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            if (normalizedTime == 0) return 0;
            if (normalizedTime == 1) return 1;
            return -Math.Pow(2, 10 * normalizedTime - 10) * Math.Sin((normalizedTime * 10 - 10.75) * (2 * Math.PI) / 3);
        }
    }

    public class OutElasticEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            if (normalizedTime == 0) return 0;
            if (normalizedTime == 1) return 1;
            return Math.Pow(2, -10 * normalizedTime) * Math.Sin((normalizedTime * 10 - 0.75) * (2 * Math.PI) / 3) + 1;
        }
    }

    public class InOutElasticEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            if (normalizedTime == 0) return 0;
            if (normalizedTime == 1) return 1;
            return normalizedTime < 0.5
                ? -(Math.Pow(2, 20 * normalizedTime - 10) * Math.Sin((20 * normalizedTime - 11.125) * (2 * Math.PI) / 4.5)) / 2
                : Math.Pow(2, -20 * normalizedTime + 10) * Math.Sin((20 * normalizedTime - 11.125) * (2 * Math.PI) / 4.5) / 2 + 1;
        }
    }

    public class InBounceEasingFunction : IEasingFunction
    {
        private readonly OutBounceEasingFunction _outBounce = new OutBounceEasingFunction();

        public double Ease(double normalizedTime) =>
            1 - _outBounce.Ease(1 - normalizedTime);
    }

    public class OutBounceEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            double n1 = 7.5625;
            double d1 = 2.75;

            if (normalizedTime < 1 / d1)
            {
                return n1 * normalizedTime * normalizedTime;
            }
            else if (normalizedTime < 2 / d1)
            {
                return n1 * (normalizedTime -= 1.5 / d1) * normalizedTime + 0.75;
            }
            else if (normalizedTime < 2.5 / d1)
            {
                return n1 * (normalizedTime -= 2.25 / d1) * normalizedTime + 0.9375;
            }
            else
            {
                return n1 * (normalizedTime -= 2.625 / d1) * normalizedTime + 0.984375;
            }
        }
    }

    public class InOutBounceEasingFunction : IEasingFunction
    {
        public double Ease(double normalizedTime)
        {
            return normalizedTime < 0.5
                ? (1 - new OutBounceEasingFunction().Ease(1 - 2 * normalizedTime)) / 2
                : (1 + new OutBounceEasingFunction().Ease(2 * normalizedTime - 1)) / 2;
        }
    }
    #endregion

    public enum AnimationStatus
    {
        Stopped,
        Playing,
        Paused
    }

    public abstract class Animation
    {
        // 新增字段
        public TimeSpan ActualStartTime { get; private set; }
        public bool BeginTimePassed { get; private set; }
        public TimeSpan StartTime { get; private set; }
        protected TimeSpan _pausedTime;
        protected TimeSpan _totalPausedDuration;
        protected double Progress { get; private set; }
        protected int CurrentLoop;

        public int Loop { get; set; } = 1;
        public bool KeepEnd { get; set; }
        public AnimationStatus Status { get; set; } = AnimationStatus.Stopped;
        public IEasingFunction EasingFunction { get; set; } = new LinearEasingFunction();
        public TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(1);
        public double SpeedMultiplier { get; private set; } = 1.0;
        public TimeSpan BeginTime { get; set; }

        public abstract Action<Animation> Started { get; set; }
        public abstract Action<Animation> Paused { get; set; }
        public abstract Action<Animation> Stopped { get; set; }
        public abstract Action<Animation> Progressing { get; set; }

        public virtual void Begin(TimeSpan timeSpan)
        {
            if (Status == AnimationStatus.Playing) return;

            Status = AnimationStatus.Playing;
            CurrentLoop = 1;
            StartTime = timeSpan;
            _totalPausedDuration = TimeSpan.Zero;
        }

        public virtual void Resume(TimeSpan timeSpan)
        {
            if (Status != AnimationStatus.Paused) return;

            _totalPausedDuration += timeSpan - _pausedTime;
            Status = AnimationStatus.Playing;
        }

        public virtual void Pause(TimeSpan timeSpan)
        {
            if (Status != AnimationStatus.Playing) return;

            Status = AnimationStatus.Paused;
            _pausedTime = timeSpan;
        }

        public virtual void Stop()
        {
            Status = AnimationStatus.Stopped;
            BeginTimePassed = false;
        }

        public virtual void Update(TimeSpan timeSpan)
        {
            if (Status != AnimationStatus.Playing) return;

            var elapsed = timeSpan - StartTime - _totalPausedDuration;
            var scaledElapsed = elapsed.TotalMilliseconds * SpeedMultiplier;

            // 处理开始时间偏移
            if (!BeginTimePassed)
            {
                if (scaledElapsed >= BeginTime.TotalMilliseconds)
                {
                    ActualStartTime = timeSpan - TimeSpan.FromMilliseconds(scaledElapsed - BeginTime.TotalMilliseconds);
                    BeginTimePassed = true;
                }
            }

            var effectiveElapsed = timeSpan - ActualStartTime - _totalPausedDuration;
            var scaledEffectiveElapsed = effectiveElapsed.TotalMilliseconds * SpeedMultiplier;
            var progress = scaledEffectiveElapsed / Duration.TotalMilliseconds;

            // 处理速度影响下的循环逻辑
            while (progress >= 1 && ShouldContinueLoop())
            {
                CurrentLoop++;
                ActualStartTime = timeSpan - TimeSpan.FromMilliseconds(
                    (scaledEffectiveElapsed % Duration.TotalMilliseconds) / SpeedMultiplier);
                progress = (timeSpan - ActualStartTime - _totalPausedDuration).TotalMilliseconds * SpeedMultiplier
                         / Duration.TotalMilliseconds;
            }
            progress = EasingFunction.Ease(progress);

            Progress = progress;
            OnUpdate(timeSpan);
            if (!ShouldContinueLoop() && progress >= 1)
            {
                Stop();
                return;
            }
        }

        protected virtual bool ShouldContinueLoop()
        {
            return Loop == -1 || CurrentLoop < Loop;
        }

        public void SetSpeedMultiplier(double multiplier, TimeSpan currentTime)
        {
            if (multiplier <= 0) throw new ArgumentOutOfRangeException();
            if (multiplier == SpeedMultiplier) return;

            if (Status == AnimationStatus.Playing)
            {
                // 重新计算起始时间保持进度连续
                var elapsed = currentTime - StartTime - _totalPausedDuration;
                var scaledElapsed = elapsed.TotalMilliseconds * SpeedMultiplier;
                var progress = scaledElapsed / Duration.TotalMilliseconds;

                var newElapsed = progress * Duration.TotalMilliseconds / multiplier;
                StartTime = currentTime - TimeSpan.FromMilliseconds(newElapsed);
                _totalPausedDuration = TimeSpan.Zero;
            }

            SpeedMultiplier = multiplier;
        }
        
        protected virtual void OnUpdate(TimeSpan timeSpan)
        {
        }
    }

    public class DoubleAnimation : Animation
    {
        public double From { get; set; }
        public double To { get; set; }
        public double Value { get; private set; }
        public override Action<Animation> Started { get; set; }
        public override Action<Animation> Paused { get; set; }
        public override Action<Animation> Stopped { get; set; }
        public override Action<Animation> Progressing { get; set; }

        public override void Begin(TimeSpan timeSpan)
        {
            base.Begin(timeSpan);
            Value = From;
            Started?.Invoke(this);
        }
        public override void Pause(TimeSpan timeSpan)
        {
            base.Pause(timeSpan);
            Paused?.Invoke(this);
        }
        public override void Stop()
        {
            base.Stop();
            Stopped?.Invoke(this);
        }

        protected override void OnUpdate(TimeSpan timeSpan)
        {
            Value = From + (To - From) * Progress;
            Progressing?.Invoke(this);
        }
    }

    public class KeyFrame<T>
    {
        public T Value { get; }
        public TimeSpan Time { get; }

        public KeyFrame(T value, TimeSpan time)
        {
            Value = value;
            Time = time;
        }
    }

    public class KeyFramesAnimation<T> : Animation
    {
        public override Action<Animation> Started { get; set; }
        public override Action<Animation> Paused { get; set; }
        public override Action<Animation> Stopped { get; set; }
        public override Action<Animation> Progressing { get; set; }

        private List<KeyFrame<T>> _keyFrames = new List<KeyFrame<T>>();
        public List<KeyFrame<T>> KeyFrames { get { return _keyFrames; } }

        public KeyFrame<T> CurrentFrame { get; private set; }
        public T Value { get => CurrentFrame ==null ? default(T) : CurrentFrame.Value; }

        public override void Begin(TimeSpan timeSpan)
        {
            base.Begin(timeSpan);
            if (Duration.Equals(new TimeSpan()))
                Duration = _keyFrames.Last().Time; // 自动设置总时长为最后一帧时间
            CurrentFrame = null;
            Started?.Invoke(this);
        }
        public override void Pause(TimeSpan timeSpan)
        {
            base.Pause(timeSpan);
            Paused?.Invoke(this);
        }
        public override void Stop()
        {
            base.Stop();
            Stopped?.Invoke(this);
        }

        protected override void OnUpdate(TimeSpan timeSpan)
        {
            double per = Progress;
            if(Loop != CurrentLoop)
            {
                while (per > 1.0)
                    per -= 1.0;
            }
            else if(per > 1.0)
                per = 1.0;
            List<double> values = new List<double>();
            foreach (var item in KeyFrames)
            {
                values.Add(item.Time.TotalMilliseconds / Duration.TotalMilliseconds);
            }
            int index = 0;
            for (int i = 0; i < values.Count && per > values[i]; i++)
                index = i;
            CurrentFrame = KeyFrames[index];
            Progressing?.Invoke(this);
        }
    }

    public class Storyboard
    {
        private TimeSpan _startTime;
        private TimeSpan _pausedTime;
        private TimeSpan _totalPausedDuration;
        private bool _hasBegun;

        public List<Animation> Animations { get; } = new List<Animation>();
        public AnimationStatus Status { get; private set; } = AnimationStatus.Stopped;
        public double SpeedMultiplier { get; private set; } = 1.0;
        public int Loop { get; set; } = 1;
        public int CurrentLoop { get; set; }
        public TimeSpan Duration { get; set; }

        public Action<Storyboard> Began;
        public Action<Storyboard> Paused;
        public Action<Storyboard> Resumed;
        public Action<Storyboard> Completed;
        public Action<Storyboard> Progressed;

        public void AddAnimation(Animation animation)
        {
            if (Status != AnimationStatus.Stopped)
                throw new InvalidOperationException("Cannot add animation while storyboard is running");

            Animations.Add(animation);
        }

        public void Begin(TimeSpan globalTime)
        {
            Begin(globalTime, 1);
        }
        public void Begin(TimeSpan globalTime, int loop)
        {
            Status = AnimationStatus.Playing;
            _startTime = globalTime;
            _totalPausedDuration = TimeSpan.Zero;
            _hasBegun = true;
            CurrentLoop = loop;

            foreach (var animation in Animations)
            {
                animation.Begin(globalTime);
            }

            Began?.Invoke(this);
        }

        public void Pause(TimeSpan globalTime)
        {
            if (Status != AnimationStatus.Playing) return;

            Status = AnimationStatus.Paused;
            _pausedTime = globalTime;

            foreach (var animation in Animations)
            {
                animation.Pause(globalTime);
            }

            Paused?.Invoke(this);
        }

        public void Resume(TimeSpan globalTime)
        {
            if (Status != AnimationStatus.Paused) return;

            Status = AnimationStatus.Playing;
            _totalPausedDuration += globalTime - _pausedTime;

            foreach (var animation in Animations)
            {
                animation.Resume(globalTime);
            }

            Resumed?.Invoke(this);
        }

        public void Stop()
        {
            if (Status == AnimationStatus.Stopped) return;

            Status = AnimationStatus.Stopped;

            foreach (var animation in Animations)
            {
                animation.Stop();
            }

            Completed?.Invoke(this);
        }

        public void Update(TimeSpan globalTime)
        {
            if (Status != AnimationStatus.Playing) return;

            var elapsed = globalTime - _startTime - _totalPausedDuration;
            var scaledElapsed = elapsed.TotalMilliseconds * SpeedMultiplier;
            var progress = scaledElapsed / Duration.TotalMilliseconds;

            // 处理速度影响下的循环逻辑
            bool newLoop = false;
            while (progress >= 1 && (Loop == -1 || CurrentLoop < Loop))
            {
                newLoop = true;
                CurrentLoop++;
                _startTime = globalTime - TimeSpan.FromMilliseconds(
                    (scaledElapsed % Duration.TotalMilliseconds) / SpeedMultiplier);
                progress = (globalTime - _startTime - _totalPausedDuration).TotalMilliseconds * SpeedMultiplier
                         / Duration.TotalMilliseconds;
            }

            bool allCompleted = true;
            foreach (var animation in Animations)
            {
                if (animation.Status == AnimationStatus.Stopped) continue;

                // 考虑BeginTime的实际开始时间
                var animStartTime = animation.BeginTimePassed ?
                    animation.ActualStartTime :
                    animation.StartTime + animation.BeginTime;

                var animElapsed = TimeSpan.FromMilliseconds(scaledElapsed) -
                                (animStartTime - animation.StartTime);

                //animation.Update(animStartTime + animElapsed);
                animation.Update(globalTime);

                if (animation.Status != AnimationStatus.Stopped)
                    allCompleted = false;
            }

            Progressed?.Invoke(this);

            if (progress >= 1 && _hasBegun && CurrentLoop == Loop)
            {
                Stop();
            }
            else if(allCompleted && CurrentLoop != Loop)
            {
                foreach (var animation in Animations)
                    animation.Begin(globalTime);
            }
        }

        public void SetSpeedMultiplier(double multiplier, TimeSpan currentTimespan)
        {
            if (multiplier <= 0) throw new ArgumentOutOfRangeException();
            SpeedMultiplier = multiplier;

            foreach (var animation in Animations)
            {
                animation.SetSpeedMultiplier(multiplier, currentTimespan);
            }
        }

        public void Seek(TimeSpan position)
        {
            if (Status == AnimationStatus.Playing)
                throw new InvalidOperationException("Cannot seek while playing");

            var simulatedTime = _startTime + position;
            foreach (var animation in Animations)
            {
                animation.Stop();
                animation.Begin(simulatedTime);
                animation.Update(simulatedTime);
                animation.Stop();
            }
        }
    }

}

 

posted on 2025-04-02 18:04  HotSky  阅读(4)  评论(0)    收藏  举报