| File: Animations\Animation.cs | Web Access |
| Project: src\src\Core\src\Core.csproj (Microsoft.Maui) |
using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Threading; namespace Microsoft.Maui.Animations { /// <summary> /// Represents an animation. /// </summary> public class Animation : IDisposable, IEnumerable { /// <summary> /// Instantiate a new <see cref="Animation"/> object. /// </summary> public Animation() { } /// <summary> /// Instantiate a new <see cref="Animation"/> object with the given parameters. /// </summary> /// <param name="callback">The <see cref="Action{T}"/> that is invoked after each tick of this animation.</param> /// <param name="start">Specifies a delay (in seconds) taken into account before the animation starts.</param> /// <param name="duration">Specifies the duration that this animation should take in seconds.</param> /// <param name="easing">The easing function to apply to this animation.</param> /// <param name="finished">A callback <see cref="Action{T}"/> that is invoked after the animation has finished.</param> public Animation(Action<double> callback, double start = 0.0f, double duration = 1.0f, Easing? easing = null, Action? finished = null) { StartDelay = start; Duration = duration; Finished = finished; Easing = easing ?? Easing.Default; Step = callback; } /// <summary> /// Instantiate a new <see cref="Animation"/> object that consists of the given list of child animations. /// </summary> /// <param name="animations">A <see cref="List{T}"/> that contains <see cref="Animation"/> objects that will be children of the newly instantiated animation.</param> public Animation(List<Animation> animations) { childrenAnimations = animations; } internal WeakReference<IAnimator>? Parent { get; set; } /// <summary> /// A callback that is invoked when this animation finishes. /// </summary> public Action? Finished { get; set; } /// <summary> /// A callback that is invoked after each tick of this animation. /// </summary> public Action<double>? Step { get; set; } bool _paused; /// <summary> /// Specifies whether this animation is currently paused. /// </summary> public bool IsPaused => _paused; /// <summary> /// Collection of child animations associated to this animation. /// </summary> protected List<Animation> childrenAnimations = new(); /// <summary> /// The name of this animation. /// </summary> public string? Name { get; set; } /// <summary> /// The delay (in seconds) taken into account before the animation starts. /// </summary> public double StartDelay { get; set; } /// <summary> /// The duration of this animation in seconds. /// </summary> public double Duration { get; set; } /// <summary> /// The current timestamp (in seconds) of the animation. /// </summary> public double CurrentTime { get; protected set; } /// <summary> /// Progress of this animation in percentage. /// </summary> public double Progress { get; protected set; } /// <summary> /// The <see cref="Easing"/> function that is applied to this animation. /// </summary> public Easing Easing { get; set; } = Easing.Default; /// <summary> /// Specifies whether this animation has finished. /// </summary> public bool HasFinished { get; protected set; } /// <summary> /// Specifies whether this animation should repeat. /// </summary> public bool Repeats { get; set; } double _skippedSeconds; int _usingResource; /// <summary> /// Provides an <see cref="IEnumerator"/> of the child animations. /// </summary> /// <returns><see cref="IEnumerator"/> of <see cref="Animation"/></returns> public IEnumerator GetEnumerator() => childrenAnimations.GetEnumerator(); /// <summary> /// Adds a new child animation to this animation with the specified parameters. /// </summary> /// <param name="beginAt">Specifies a delay (in seconds) taken into account before the added child animation starts.</param> /// <param name="duration">Specifies the duration (in seconds) that the added child animation should take.</param> /// <param name="animation">The <see cref="Animation"/> object to add to this animation as a child.</param> /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="beginAt"/> or <paramref name="duration"/> is less than 0 or more than 1.</exception> /// <exception cref="ArgumentException">Thrown when <paramref name="duration"/> is less than or equal to <paramref name="beginAt"/>.</exception> public void Add(double beginAt, double duration, Animation animation) { if (beginAt < 0 || beginAt > 1) throw new ArgumentOutOfRangeException(nameof(beginAt)); if (duration < 0 || duration > 1) throw new ArgumentOutOfRangeException(nameof(duration)); if (duration <= beginAt) throw new ArgumentException($"{nameof(duration)} must be greater than {nameof(beginAt)}"); animation.StartDelay = beginAt; animation.Duration = duration; childrenAnimations.Add(animation); } /// <summary> /// Method to trigger an update for this animation. /// </summary> /// <param name="milliseconds">Number of milliseconds that have passed since the last tick.</param> public void Tick(double milliseconds) { if (IsPaused) return; if (0 == Interlocked.Exchange(ref _usingResource, 1)) { try { OnTick(_skippedSeconds + milliseconds); _skippedSeconds = 0; } finally { //Release the lock Interlocked.Exchange(ref _usingResource, 0); } } //animation is lagging behind! else { _skippedSeconds += milliseconds; } } /// <summary> /// A reference to the <see cref="IAnimationManager"/> that manages this animation. /// </summary> public IAnimationManager? AnimationManager => animationManger; /// <summary> /// A reference to the <see cref="IAnimationManager"/> that manages this animation. /// </summary> protected IAnimationManager? animationManger; /// <summary> /// Executes the logic to update all animations within this animation. /// </summary> /// <param name="millisecondsSinceLastUpdate">Number of milliseconds that have passed since the last tick.</param> protected virtual void OnTick(double millisecondsSinceLastUpdate) { if (HasFinished) return; var secondsSinceLastUpdate = millisecondsSinceLastUpdate / 1000.0; CurrentTime += secondsSinceLastUpdate; if (childrenAnimations.Count > 0) { var hasFinished = true; foreach (var animation in childrenAnimations) { animation.OnTick(millisecondsSinceLastUpdate); if (!animation.HasFinished) hasFinished = false; } HasFinished = hasFinished; } else { var start = CurrentTime - StartDelay; if (CurrentTime < StartDelay) return; var percent = Math.Min(start / Duration, 1); Update(percent); } if (HasFinished) { Finished?.Invoke(); if (Repeats) Reset(); } } /// <summary> /// Updates this animation by updating <see cref="Progress"/> and invoking <see cref="Step"/>. /// </summary> /// <param name="percent">Progress of this animation in percentage.</param> public virtual void Update(double percent) { try { Progress = Easing.Ease(percent); Step?.Invoke(Progress); HasFinished = percent == 1; } catch (Exception) { HasFinished = true; } } /// <summary> /// Sets the <see cref="IAnimationManager"/> for this animation. /// </summary> /// <param name="animationManger">Reference to the <see cref="IAnimationManager"/> that will manage this animation.</param> public void Commit(IAnimationManager animationManger) { this.animationManger = animationManger; animationManger.Add(this); } /// <summary> /// Creates an animation that includes both the original animation and a reversed version of the same animation. /// </summary> /// <returns>An <see cref="Animation"/> object with the original animation and the reversed animation.</returns> /// <remarks>You can get just the reversed animation by using <see cref="CreateReverse"/>.</remarks> public Animation CreateAutoReversing() { var reversedChildren = childrenAnimations.ToList(); reversedChildren.Reverse(); var reversed = CreateReverse(); var parentAnimation = new Animation { Duration = reversed.StartDelay + reversed.Duration, Repeats = Repeats, childrenAnimations = { this, reversed, } }; Repeats = false; return parentAnimation; } /// <summary> /// Creates a reversed version of the current animation, including reversing the child animations. /// </summary> /// <returns>An <see cref="Animation"/> object that is the reversed version of this animation.</returns> /// <remarks>You can the forward and reverse animation by using <see cref="CreateAutoReversing"/>.</remarks> protected virtual Animation CreateReverse() { var reversedChildren = childrenAnimations.ToList(); reversedChildren.Reverse(); return new Animation { Easing = Easing, Duration = Duration, StartDelay = StartDelay + Duration, childrenAnimations = reversedChildren, }; } /// <summary> /// Resets the animation (and all child animations) to its initial state. /// </summary> public virtual void Reset() { CurrentTime = 0; HasFinished = false; foreach (var x in childrenAnimations) x.Reset(); } /// <summary> /// Pauses the animation. /// </summary> public void Pause() { _paused = true; animationManger?.Remove(this); } /// <summary> /// Resumes the animation. /// </summary> public void Resume() { _paused = false; animationManger?.Add(this); } /// <summary> /// Removes this animation from it's parent. /// If there is no parent, nothing will happen. /// </summary> public void RemoveFromParent() { IAnimator? view = null; if (Parent?.TryGetTarget(out view) ?? false) view?.RemoveAnimation(this); } /// <summary> /// Gets a value that specifies if this animation has been disposed. /// </summary> public bool IsDisposed => _disposedValue; private bool _disposedValue; // To detect redundant calls protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { foreach (var child in childrenAnimations) child.Dispose(); childrenAnimations.Clear(); } _disposedValue = true; animationManger?.Remove(this); Finished = null; Step = null; } } /// <inheritdoc/> public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); } internal virtual void ForceFinish() { if (Progress < 1.0) { Update(1.0); } } } }