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:
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
Hi Kirill,
ReplyDeleteNice helper ...!
There's a great library called ArtefactAnimator (http://artefactanimator.codeplex.com) which is also very helpfull in doing "on the fly animations" ...
Regards
Teejay
Hi Teejay,
ReplyDeleteMany Thanks for the link!
Cheers,
Kirill