diff --git a/src/Avalonia.Animation/AnimationInstance`1.cs b/src/Avalonia.Animation/AnimationInstance`1.cs index bc7e106a2a..6f601a3e13 100644 --- a/src/Avalonia.Animation/AnimationInstance`1.cs +++ b/src/Avalonia.Animation/AnimationInstance`1.cs @@ -19,6 +19,7 @@ namespace Avalonia.Animation private ulong? _iterationCount; private ulong _currentIteration; private bool _gotFirstKFValue; + private bool _playbackReversed; private FillMode _fillMode; private PlaybackDirection _playbackDirection; private Animator _animator; @@ -160,9 +161,14 @@ namespace Avalonia.Animation _currentIteration = (ulong)(opsTime / iterationTime); - // Stop animation when the current iteration is beyond the iteration count. + // Stop animation when the current iteration is beyond the iteration count + // and snap the last iteration value to exact values. if ((_currentIteration + 1) > _iterationCount) + { + var easedTime = _easeFunc.Ease(_playbackReversed ? 0.0 : 1.0); + _lastInterpValue = _interpolator(easedTime, _neutralValue); DoComplete(); + } if (playbackTime <= iterDuration) { @@ -170,27 +176,26 @@ namespace Avalonia.Animation var normalizedTime = playbackTime / iterDuration; // Check if normalized time needs to be reversed according to PlaybackDirection - - bool playbackReversed; + switch (_playbackDirection) { case PlaybackDirection.Normal: - playbackReversed = false; + _playbackReversed = false; break; case PlaybackDirection.Reverse: - playbackReversed = true; + _playbackReversed = true; break; case PlaybackDirection.Alternate: - playbackReversed = (_currentIteration % 2 == 0) ? false : true; + _playbackReversed = (_currentIteration % 2 == 0) ? false : true; break; case PlaybackDirection.AlternateReverse: - playbackReversed = (_currentIteration % 2 == 0) ? true : false; + _playbackReversed = (_currentIteration % 2 == 0) ? true : false; break; default: throw new InvalidOperationException($"Animation direction value is unknown: {_playbackDirection}"); } - if (playbackReversed) + if (_playbackReversed) normalizedTime = 1 - normalizedTime; // Ease and interpolate diff --git a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs index d89b8469df..f7a8774689 100644 --- a/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs +++ b/tests/Avalonia.Animation.UnitTests/AnimationIterationTests.cs @@ -8,6 +8,7 @@ using Avalonia.Styling; using Avalonia.UnitTests; using Avalonia.Data; using Xunit; +using Avalonia.Animation.Easings; namespace Avalonia.Animation.UnitTests { @@ -73,5 +74,58 @@ namespace Avalonia.Animation.UnitTests Assert.True(animationRun.Status == TaskStatus.RanToCompletion); Assert.Equal(border.Width, 100d); } + + [Fact] + public void Check_FillModes_Start_and_End_Values_if_Retained() + { + var keyframe1 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 0d), + }, + Cue = new Cue(0.0d) + }; + + var keyframe2 = new KeyFrame() + { + Setters = + { + new Setter(Border.WidthProperty, 300d), + }, + Cue = new Cue(1.0d) + }; + + var animation = new Animation() + { + Duration = TimeSpan.FromSeconds(0.05d), + Delay = TimeSpan.FromSeconds(0.05d), + Easing = new SineEaseInOut(), + FillMode = FillMode.Both, + Children = + { + keyframe1, + keyframe2 + } + }; + + var border = new Border() + { + Height = 100d, + Width = 100d, + }; + + var clock = new TestClock(); + var animationRun = animation.RunAsync(border, clock); + + clock.Step(TimeSpan.FromSeconds(0d)); + Assert.Equal(border.Width, 0d); + + clock.Step(TimeSpan.FromSeconds(0.050d)); + Assert.Equal(border.Width, 0d); + + clock.Step(TimeSpan.FromSeconds(0.100d)); + Assert.Equal(border.Width, 300d); + } } }