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();
}
}
}
}