惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
A
About on SuperTechFans
博客园 - Franky
Engineering at Meta
Engineering at Meta
Recent Announcements
Recent Announcements
云风的 BLOG
云风的 BLOG
B
Blog
Microsoft Security Blog
Microsoft Security Blog
L
LangChain Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
U
Unit 42
Martin Fowler
Martin Fowler
Y
Y Combinator Blog
Stack Overflow Blog
Stack Overflow Blog
博客园 - 叶小钗
Vercel News
Vercel News
Apple Machine Learning Research
Apple Machine Learning Research
The Cloudflare Blog
Last Week in AI
Last Week in AI
腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
爱范儿
爱范儿
V
V2EX
G
Google Developers Blog

博客园 - HotSky

WPF游标直尺 Chrome启用CDP(chrome-devtools-protocol)进行远程操控 WPF 让ScrollViewer支持按住鼠标中键拖拽滚动内容 WPF 让ScrollViewer支持鼠标中键滚动缩放内容 Nodejs使用 nodejs中写sql需要用in时的写法 C#城市最短路径 Sqlite PDF附录A: 内容流操作码 WPF FPS类 生命模拟 C# Sql帮助类,可扩展 WPF WriteableBitmap通过GDI+绘制帮助类 Alpha混合 Bmp读写二值图 WPF支持任意快捷键+鼠标组合的绑定类 WPF阻止窗体被系统缩放,使用显示器DPI WPF DataGrid自动增长序号列 C#访问或修改私有类、函数、变量、属性 WPF一个简单的属性编辑控件
C#Animation
HotSky · 2025-04-02 · via 博客园 - HotSky
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();
            }
        }
    }

}