WPF - sequential animation simple example

11,039

Solution 1

There should be an event ani.Completed - handle that event and start the next phase of the animation, then start the first one running and each phase will trigger the next.

ColorAnimation ani = // whatever...

ani.Completed += (s, e) => 
   {
       ColorAnimation ani2 = // another one...

       // and so on
   };

newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);

UPDATE:

public partial class Window1 : Window
{
    Rectangle blueRect;
    Rectangle redRect;
    Rectangle greenRect;
    Rectangle yellowRect;

    public Window1()
    {
        InitializeComponent();
        blueRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Blue, Name = "Blue" };
        redRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Red, Name = "Yellow" };
        greenRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Green, Name = "Green" };
        yellowRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Yellow, Name = "Yellow" };

        UniformGrid1.Children.Add(blueRect);
        UniformGrid1.Children.Add(redRect);
        UniformGrid1.Children.Add(greenRect);
        UniformGrid1.Children.Add(yellowRect);
    }

    IEnumerable<Action<Action>> AnimationSequence()
    {
        for (; ; )
        {
            yield return AnimateCell(blueRect, Colors.Blue);
            yield return AnimateCell(redRect, Colors.Red);
            yield return AnimateCell(greenRect, Colors.Green);
            yield return AnimateCell(yellowRect, Colors.Yellow);
        }
    }

    private IEnumerator<Action<Action>> _actions;

    private void RunNextAction()
    {
        if (_actions.MoveNext())
            _actions.Current(RunNextAction);
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        _actions = AnimationSequence().GetEnumerator();
        RunNextAction();
    }

    private Action<Action> AnimateCell(Rectangle rectangle, Color fromColor)
    {
        return completed =>
        {
            Color toColor = Colors.White;
            ColorAnimation ani = new ColorAnimation(toColor, 
                                    new Duration(TimeSpan.FromMilliseconds(300)));
            ani.AutoReverse = true;
            ani.Completed += (s, e) => completed();

            SolidColorBrush newBrush = new SolidColorBrush(fromColor);
            ani.BeginTime = TimeSpan.FromSeconds(2);
            rectangle.Fill = newBrush;
            newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);
        };
    }
}

Try pasting the above into your program. It does what you need, but in a way that may be useful to you in other contexts. It's still event driven, but it uses an "iterator method" (with yield return) to create the impression that it is sequential coding that blocks while the animation is going on.

The nice thing about this is that you can play around with the AnimationSequence method in a very intuitive way - you could write out the timeline of the animation in a series of statements, or use loops, or whatever you want.

Solution 2

The solution I've tried is to use a Queue like so. This will let you add to the animation chain dynamically. I'm not sure if the lock is necessary, but I left it in just to be safe.

Queue<Object[]> animationQueue = new Queue<Object[]>();

void sequentialAnimation(DoubleAnimation da, Animatable a, DependencyProperty dp)
{
    da.Completed += new EventHandler(da_Completed);

    lock (animationQueue)
    {
        if (animationQueue.Count == 0) // no animation pending
        {
            animationQueue.Enqueue(new Object[] { da, a, dp });
            a.BeginAnimation(dp, da);
        }
        else
        {
            animationQueue.Enqueue(new Object[] { da, a, dp });
        }
    }
}

void da_Completed(object sender, EventArgs e)
{
    lock (animationQueue)
    {
        Object[] completed = animationQueue.Dequeue();
        if (animationQueue.Count > 0)
        {
            Object[] next = animationQueue.Peek();
            DoubleAnimation da = (DoubleAnimation)next[0];
            Animatable a = (Animatable)next[1];
            DependencyProperty dp = (DependencyProperty)next[2];

            a.BeginAnimation(dp, da);
        }
    }
}

Solution 3

This can be accomplished by using a class with the contradictory name ParallelTimeline and carefully adjusting the BeginTime property. Note in the example below how the BeginTime property of the second DoubleAnimation is set to the duration of the first.

<ParallelTimeline>
      <DoubleAnimation
           Storyboard.TargetName="FlashRectangle" 
           Storyboard.TargetProperty="Opacity"
           From="0.0" To="1.0" Duration="0:0:1"/>
      <DoubleAnimation BeginTime="0:0:0.05"
           Storyboard.TargetName="FlashRectangle" 
           Storyboard.TargetProperty="Opacity"
           From="1.0" To="0.0" Duration="0:0:2"/>
 </ParallelTimeline>
Share:
11,039
David Hodgson
Author by

David Hodgson

Updated on June 15, 2022

Comments

  • David Hodgson
    David Hodgson almost 2 years

    I'm learning about WPF animation, and am confused about how to apply animations sequentially. As a simple example, I've got four rectangles in a uniform grid, and would like to change the color of each one sequentially. Here's what I have so far:

    public partial class Window1 : Window
    {
        Rectangle blueRect;
        Rectangle redRect;
        Rectangle greenRect;
        Rectangle yellowRect;
    
        public Window1()
        {
            InitializeComponent();
            blueRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Blue, Name="Blue"};
            redRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Red, Name="Yellow"};
            greenRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Green, Name="Green" };
            yellowRect = new Rectangle() { Fill = System.Windows.Media.Brushes.Yellow, Name="Yellow" };
    
            UniformGrid1.Children.Add(blueRect);
            UniformGrid1.Children.Add(redRect);
            UniformGrid1.Children.Add(greenRect);
            UniformGrid1.Children.Add(yellowRect);
    
        }
    
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            animateCell(blueRect, Colors.Blue);
            animateCell(redRect, Colors.Red);
        }
    
        private void animateCell(Rectangle rectangle, Color fromColor)
        {
            Color toColor = Colors.White;
            ColorAnimation ani = new ColorAnimation(toColor, new Duration(TimeSpan.FromMilliseconds(300)));
            ani.AutoReverse = true;
    
            SolidColorBrush newBrush = new SolidColorBrush(fromColor);
            ani.BeginTime = TimeSpan.FromSeconds(2);
            rectangle.Fill = newBrush;
            newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);
            //NameScope.GetNameScope(this).RegisterName(rectangle.Name, rectangle);
            //Storyboard board = new Storyboard();
            //board.Children.Add(ani);
            //Storyboard.SetTargetName(rectangle, rectangle.Name);
            //Storyboard.SetTargetProperty(ani, new PropertyPath(SolidColorBrush.ColorProperty));
            //board.Begin();
    
        }
    

    What's the easiest way of accomplishing this? The code in the comments is my first guess, but it's not working correctly.

  • David Hodgson
    David Hodgson over 14 years
    What if the animations are dynamic? I'd like to be able to call say blue, green, red, blue, green, red; each animation maybe 2 seconds after the previous. Is there a way to make the caller of animateCell block until ani.Completed is fired?
  • Diego Frehner
    Diego Frehner over 9 years
    A lot more readable and a queue matches the imagination of a sequence. Implemented a queue of actions where an action just starts an animation.