From 60537a36c9027a7d272332e6d302ffd0ef2bbea4 Mon Sep 17 00:00:00 2001 From: xiaoy312 Date: Thu, 30 Mar 2023 12:07:18 -0400 Subject: [PATCH] fix(animation): not defaulting starting value from animated value --- src/Uno.UI/FeatureConfiguration.cs | 9 ++++ .../Animators/AnimatorFactory.Android.cs | 47 ++++++++++++++++++- .../Media/Animation/Timeline.animation.cs | 30 ++++++++++-- 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/src/Uno.UI/FeatureConfiguration.cs b/src/Uno.UI/FeatureConfiguration.cs index ea580bd2a318..61c6cae54d89 100644 --- a/src/Uno.UI/FeatureConfiguration.cs +++ b/src/Uno.UI/FeatureConfiguration.cs @@ -771,5 +771,14 @@ public static class Cursors public static bool UseHandForInteraction { get; set; } = true; #endif } + + public static class Animation + { + /// + /// Determines if the default animation starting value + /// will be from the animated value or local value, when the From property is omitted. + /// + public static bool DefaultsStartingValueFromAnimatedValue { get; } = true; + } } } diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/Animators/AnimatorFactory.Android.cs b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/AnimatorFactory.Android.cs index 4c851942ceb1..7c98c7294234 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/Animators/AnimatorFactory.Android.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/Animators/AnimatorFactory.Android.cs @@ -332,10 +332,55 @@ private static ValueAnimator GetRelativeAnimator(Java.Lang.Object target, string } /// - /// Ensures that scale value is without the android accepted values + /// Ensures that scale value is within the android accepted values /// private static double ToNativeScale(double value) => double.IsNaN(value) ? 1 : value; + internal static bool TryGetNativeAnimatedValue(Timeline timeline, out object value) + { + value = null; + + if (timeline.GetIsDependantAnimation() || timeline.GetIsDurationZero()) + { + return false; + } + + var info = timeline.PropertyInfo.GetPathItems().Last(); + var target = info.DataContext; + var property = info.PropertyName + .Split(new[] { '.' }).Last() + .Replace("(", "").Replace(")", ""); + + if (target is Transform { IsAnimating: false }) + { + // While not animating, these native properties will be reset. + // In that case, the dp actual value should be read instead (by return false here). + return false; + } + + value = property switch + { + nameof(FrameworkElement.Opacity) when target is View view => (double)view.Alpha, + nameof(TranslateTransform.X) when target is TranslateTransform translate => ViewHelper.PhysicalToLogicalPixels(translate.View.TranslationX), + nameof(TranslateTransform.Y) when target is TranslateTransform translate => ViewHelper.PhysicalToLogicalPixels(translate.View.TranslationY), + nameof(RotateTransform.Angle) when target is RotateTransform rotate => (double)rotate.View.Rotation, + nameof(ScaleTransform.ScaleX) when target is ScaleTransform scale => (double)scale.View.ScaleX, + nameof(ScaleTransform.ScaleY) when target is ScaleTransform scale => (double)scale.View.ScaleY, + //nameof(SkewTransform.AngleX) when target is SkewTransform skew => ViewHelper.PhysicalToLogicalPixels(skew.View.ScaleX), // copied as is from GetGPUAnimator + //nameof(SkewTransform.AngleY) when target is SkewTransform skew => ViewHelper.PhysicalToLogicalPixels(skew.View.ScaleY), + nameof(CompositeTransform.TranslateX) when target is CompositeTransform composite => ViewHelper.PhysicalToLogicalPixels(composite.View.TranslationX), + nameof(CompositeTransform.TranslateY) when target is CompositeTransform composite => ViewHelper.PhysicalToLogicalPixels(composite.View.TranslationY), + nameof(CompositeTransform.Rotation) when target is CompositeTransform composite => (double)composite.View.Rotation, + nameof(CompositeTransform.ScaleX) when target is CompositeTransform composite => (double)composite.View.ScaleX, + nameof(CompositeTransform.ScaleY) when target is CompositeTransform composite => (double)composite.View.ScaleY, + //nameof(CompositeTransform.SkewX) when target is CompositeTransform composite => ViewHelper.PhysicalToLogicalPixels(composite.View.ScaleX), + //nameof(CompositeTransform.SkewY) when target is CompositeTransform composite => ViewHelper.PhysicalToLogicalPixels(composite.View.ScaleY), + + _ => null, + }; + + return value != null; + } } } diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/Timeline.animation.cs b/src/Uno.UI/UI/Xaml/Media/Animation/Timeline.animation.cs index bb1f417bc8d6..dafd6bb98fb4 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/Timeline.animation.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/Timeline.animation.cs @@ -9,6 +9,7 @@ using Uno.Foundation.Logging; using Uno.UI.DataBinding; using System.Diagnostics; +using Uno.UI; namespace Windows.UI.Xaml.Media.Animation { @@ -69,11 +70,14 @@ private TimelineState State private BindingPath PropertyInfo => _owner?.PropertyInfo; private string[] GetTraceProperties() => _owner?.GetTraceProperties(); + private void ClearValue() => _owner?.ClearValue(); private void SetValue(object value) => _owner?.SetValue(value); - private bool NeedsRepeat(Stopwatch activeDuration, int replayCount) => _owner?.NeedsRepeat(activeDuration, replayCount) ?? false; + private object GetValue() => _owner?.GetValue(); private object GetNonAnimatedValue() => _owner?.GetNonAnimatedValue(); + private bool NeedsRepeat(Stopwatch activeDuration, int replayCount) => _owner?.NeedsRepeat(activeDuration, replayCount) ?? false; + public void Begin() { if (_trace.IsEnabled) @@ -431,8 +435,9 @@ private T ComputeFromValue() } else { - var value = GetNonAnimatedValue(); - + var value = FeatureConfiguration.Animation.DefaultsStartingValueFromAnimatedValue + ? GetValueCore() + : GetNonAnimatedValue(); if (value != null) { return AnimationOwner.Convert(value); @@ -442,6 +447,25 @@ private T ComputeFromValue() return null; } + private object GetValueCore() + { +#if !__ANDROID__ + return GetValue(); +#else + // On android, animation may target a native property implementing the behavior instead of the specified dependency property. + // When starting a new animation midst another, in order to continue from the current animated value, + // we need to retrieve the value of that native property, as reading the dp value will just give the final value. + if (AnimatorFactory.TryGetNativeAnimatedValue(_owner, out var value)) + { + return value; + } + else + { + return GetValue(); + } +#endif + } + /// /// Calculates the To value of the animation /// For simplicity, animations are based on to and from values