Friday, May 4, 2012

Silverlight Double Animation Helper

Often we need to quickly animate something on the screen without initializing storyboards, animations, setting targets etc.

Also sometimes we need to be able to change animation direction or target value.

All this is about double animation.

Suppose we need to move a scroll viewer vertical offset with some speed but depending on user actions we may change speed and even direction of scrolling.

So I wrote a helper class named DoubleAnimationHelper.

You can use it as simple as:

image

Now if while this animation is in progress you call this code with different offset – the same instance of DoubleAnimationHelper class will be returned by Get factory method – and the previous animation will be graciously changed to new one (it will have the effect of continuing from current point).

Here is the code

public class DoubleAnimationHelper : DependencyObject
{
    #region Class level variables

    private DependencyObject mOwner;
    private DependencyProperty mProperty;
    private Storyboard mStoryboard = new Storyboard();
    private DoubleAnimation mAnimation = new DoubleAnimation();
    private Action mCallback = null;
    private bool mInitialized = false;

    #endregion

    #region Constructors

    private DoubleAnimationHelper(DependencyObject owner, DependencyProperty property)
    {
        mOwner = owner;
        mProperty = property;

        IsManual = true;

        CurrentValue = TargetValue = (double)owner.GetValue(property);

        mAnimation.AutoReverse = false;

        Storyboard.SetTargetProperty(mAnimation, new PropertyPath("CurrentValue"));
        Storyboard.SetTarget(mAnimation, this);

        mStoryboard.Children.Add(mAnimation);
        mStoryboard.Completed += mStoryboard_Completed;

        mInitialized = true;
    }

    #endregion

    #region FactoryMethods

    public static DoubleAnimationHelper Get(DependencyObject owner, DependencyProperty property)
    {
        var dictionary = GetAnimationHelper(owner);
        if (dictionary == null)
        {
            dictionary = new Dictionary<DependencyProperty, DoubleAnimationHelper>();
            SetAnimationHelper(owner, dictionary);
        }
        if (!dictionary.ContainsKey(property))
        {
            dictionary.Add(property, new DoubleAnimationHelper(owner, property));
        }
        return dictionary[property];
    }

    #endregion

    #region Properties

    public bool IsManual { get; set; }

    #endregion

    #region Event handlers

    private void mStoryboard_Completed(object sender, EventArgs e)
    {
        if (mCallback != null)
        {
            mCallback();
            mCallback = null;
        }
    }

    #endregion

    #region Methods

    public void Animate()
    {
        Animate(null);
    }

    public void Animate(Action callback)
    {
        if (mInitialized)
        {
            mStoryboard.Stop();

            mAnimation.From = CurrentValue;
            mAnimation.To = TargetValue;
            mAnimation.Duration = Duration;
            mAnimation.EasingFunction = EasingFunction;
            mCallback = callback;

            mStoryboard.Begin();
        }
    }

    public void Stop()
    {
        if (mInitialized)
        {
            mStoryboard.Pause();
        }
    }

    #endregion

    #region Overrides

    #endregion

    #region Dependency properties

    #region AnimationHelper

    internal static Dictionary<DependencyProperty, DoubleAnimationHelper> GetAnimationHelper(DependencyObject element)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        return (Dictionary<DependencyProperty, DoubleAnimationHelper>)element.GetValue(AnimationHelperProperty);
    }

    internal static void SetAnimationHelper(DependencyObject element, Dictionary<DependencyProperty, DoubleAnimationHelper> value)
    {
        if (element == null)
        {
            throw new ArgumentNullException("element");
        }
        element.SetValue(AnimationHelperProperty, value);
    }

    internal static readonly DependencyProperty AnimationHelperProperty =
        DependencyProperty.RegisterAttached(
            "AnimationHelper",
            typeof(Dictionary<DependencyProperty, DoubleAnimationHelper>),
            typeof(DoubleAnimationHelper),
            new PropertyMetadata(null, OnAnimationHelperPropertyChanged));

    private static void OnAnimationHelperPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
            
    }

    #endregion

    #region TargetValue

    public double TargetValue
    {
        get { return (double)GetValue(TargetValueProperty); }
        set { SetValue(TargetValueProperty, value); }
    }

    public static readonly DependencyProperty TargetValueProperty =
        DependencyProperty.Register(
            "TargetValue",
            typeof(double),
            typeof(DoubleAnimationHelper),
            new PropertyMetadata((double)0, OnTargetValuePropertyChanged));

    private static void OnTargetValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var control = sender as DoubleAnimationHelper;
        if (control != null)
        {
            control.TargetValueUpdated();
        }
    }

    private void TargetValueUpdated()
    {
        if (!IsManual)
        {
            Animate();
        }
    }

    #endregion

    #region CurrentValue

    public double CurrentValue
    {
        get { return (double)GetValue(CurrentValueProperty); }
        set { SetValue(CurrentValueProperty, value); }
    }

    public static readonly DependencyProperty CurrentValueProperty =
        DependencyProperty.Register(
            "CurrentValue",
            typeof(double),
            typeof(DoubleAnimationHelper),
            new PropertyMetadata((double)0, OnCurrentValuePropertyChanged));

    private static void OnCurrentValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var control = sender as DoubleAnimationHelper;
        if (control != null)
        {
            control.CurrentValueUpdated();
        }
    }

    private void CurrentValueUpdated()
    {
        if (mProperty == ScrollViewer.VerticalOffsetProperty)
        {
            ((ScrollViewer)mOwner).ScrollToVerticalOffset(CurrentValue);
        }
        else if (mProperty == ScrollViewer.HorizontalOffsetProperty)
        {
            ((ScrollViewer)mOwner).ScrollToHorizontalOffset(CurrentValue);
        }
        else
        {
            mOwner.SetValue(mProperty, CurrentValue);
        }
    }

    #endregion

    #region Duration

    public Duration Duration
    {
        get { return (Duration)GetValue(DurationProperty); }
        set { SetValue(DurationProperty, value); }
    }

    public static readonly DependencyProperty DurationProperty =
        DependencyProperty.Register(
            "Duration",
            typeof(Duration),
            typeof(DoubleAnimationHelper),
            new PropertyMetadata(new Duration(TimeSpan.FromMilliseconds(200)), OnDurationPropertyChanged));

    private static void OnDurationPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var control = sender as DoubleAnimationHelper;
        if (control != null)
        {
            control.DurationUpdated();
        }
    }

    private void DurationUpdated()
    {
        if (!IsManual)
        {
            Animate();
        }
    }

    #endregion

    #region EasingFunction

    public IEasingFunction EasingFunction
    {
        get { return (IEasingFunction)GetValue(EasingFunctionProperty); }
        set { SetValue(EasingFunctionProperty, value); }
    }

    public static readonly DependencyProperty EasingFunctionProperty =
        DependencyProperty.Register(
            "EasingFunction",
            typeof(IEasingFunction),
            typeof(DoubleAnimationHelper),
            new PropertyMetadata(null, OnEasingFunctionPropertyChanged));

    private static void OnEasingFunctionPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        var control = sender as DoubleAnimationHelper;
        if (control != null)
        {
            control.EasingFunctionUpdated();
        }
    }

    private void EasingFunctionUpdated()
    {
        if (!IsManual)
        {
            Animate();
        }
    }

    #endregion

    #endregion
}

 

Happy coding