Hamed
Hamed

Reputation: 307

GridLength animation using keyframes?

I want to know are there any classes that I can animate a GridLength value using KeyFrames? I have seen the following sites, but none of them were with KeyFrames:

Any advice?

Upvotes: 1

Views: 1228

Answers (2)

Christian Findlay
Christian Findlay

Reputation: 7712

It is fairly straight forward but you need to use an adapter because you can't directly animate Width on the ColumnDefinition class with a DoubleAnimator because ColumnDefinition is not a double. Here's my code:

public class ColumnDefinitionDoubleAnimationAdapter : Control
{
    #region Dependency Properties
    public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(nameof(Width), typeof(double), typeof(ColumnDefinitionDoubleAnimationAdapter), new PropertyMetadata((double)0, WidthChanged));

    private static void WidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var columnDefinitionDoubleAnimationAdapter = (ColumnDefinitionDoubleAnimationAdapter)d;
        columnDefinitionDoubleAnimationAdapter.Width = (double)e.NewValue;
    }
    #endregion

    #region Fields
    private ColumnDefinition _ColumnDefinition;
    #endregion

    #region Constructor
    public ColumnDefinitionDoubleAnimationAdapter(ColumnDefinition columnDefinition)
    {
        _ColumnDefinition = columnDefinition;
    }
    #endregion

    #region Public Properties
    public double Width
    {
        get
        {
            return (double)GetValue(WidthProperty);
        }
        set
        {
            SetValue(WidthProperty, value);
            _ColumnDefinition.Width = new GridLength(value);
        }
    }
    #endregion
}

Unfortunately the above is pretty inefficient because it creates a GridLength again and again because ColumnDefinition.Width.Value should be read only.

Here is a method to do the animation. It's important that it uses Task based async because otherwise the storyboard will go out of scope and cause bad behaviour. This is good practice anyway so you can await the animation if you need to:

    public async static Task AnimateColumnWidth(ColumnDefinition columnDefinition, double from, double to, TimeSpan duration, IEasingFunction ease)
    {
        var taskCompletionSource = new TaskCompletionSource<bool>();

        var storyboard = new Storyboard();

        var animation = new DoubleAnimation();
        animation.EasingFunction = ease;
        animation.Duration = new Duration(duration);
        storyboard.Children.Add(animation);
        animation.From = from;
        animation.To = to;

        var columnDefinitionDoubleAnimationAdapter = new ColumnDefinitionDoubleAnimationAdapter(columnDefinition);

        Storyboard.SetTarget(animation, columnDefinitionDoubleAnimationAdapter);
        Storyboard.SetTargetProperty(animation, new PropertyPath(ColumnDefinitionDoubleAnimationAdapter.WidthProperty));

        storyboard.Completed += (a, b) =>
        {
            taskCompletionSource.SetResult(true);
        };

        storyboard.Begin();

        await taskCompletionSource.Task;
    }

And an example usage:

    private async void TheMenu_HamburgerToggled(object sender, EventArgs e)
    {
        TheMenu.IsOpen = !TheMenu.IsOpen;

        var twoSeconds = TimeSpan.FromMilliseconds(120);
        var ease = new CircleEase { EasingMode = TheMenu.IsOpen ? EasingMode.EaseIn : EasingMode.EaseOut };
        if (TheMenu.IsOpen)
        {
            await UIUtilities.AnimateColumnWidth(MenuColumn, 40, 320, twoSeconds, ease);
        }
        else
        {
            await UIUtilities.AnimateColumnWidth(MenuColumn, 320, 40, twoSeconds, ease);
        }
    }

Upvotes: 0

user572559
user572559

Reputation:

Create an attached behavior and animate it instead.

Sure, GridLength clearly is not a numeric type and as such it's not clear how it can be animated. To compnesate that I can create an attached behavior like:

public class AnimatableProperties
    {
        public static readonly DependencyProperty WidthProperty =
            DependencyProperty.RegisterAttached("Width",
            typeof(double),
            typeof(DependencyObject),
            new PropertyMetadata(-1, (o, e) => 
            {
                AnimatableProperties.OnWidthChanged((Grid)o, (double)e.NewValue);
            }));

        public static void SetWidth(DependencyObject o,
            double e)
        {
            o.SetValue(AnimatableProperties.WidthProperty, e);
        }

        public static double GetWidth(DependencyObject o)
        {
            return (double)o.GetValue(AnimatableProperties.WidthProperty);
        }

        private static void OnWidthChanged(DependencyObject target,
            double e)
        {
            target.SetValue(Grid.WidthProperty, new GridLength(e));
        }
    }

That will re-inroduce Grid width as numeric property of double type. Having that in place you can freely animate it.

P.S. Obviously it doesn't make much sense to use Grid's Width as it's already double. any other GridLength based properties can be wrpapped with double wrappers as per the sample above and then animated via that wrappers.

Upvotes: 1

Related Questions