Freigeben über


Silverlight: How to use CompositionTarget.Rendering event to synchronize discrete and non-discrete animations

I’ve brought up this topic in the recent post, but I was pointed out it can be lost among other content and is not stressed enough.

Everything brings down to the fact that some actions, particularly animations cannot be easily enclosed in storyboards. For example let’s imagine that we animate an object X,Y positions. When object disappears out of the display area you would like to collapse the object visibility. This case is not that complicated. You know its start X,Y position, end X,Y position and the animation duration so you can calculate when it disappears. Things are getting much more complicated if our animation is a little bit more complex – for example:

· Together with X,Y position, we animate other properties that may affect visibility - for example - perspective (Z axis) at the same time and we wish to hide the object if it crosses -90, +90 degree boundary

· There is an easing function applied to the X,Y animation like a bouncing effect (t axis is time, f(t) axis is the progress):

 

Imagine that your object disappears after 90% of its X/Y transition. In that case it appears and disappears couple of times. It is not easy to calculate and set such animations.

And now let's assume that both bullet points are valid in one storyboard. Code with such calculations would look like a nightmare. Even more than that - If you need to apply that style of animation to many objects at the same time, the task can become so formidably complicated, that your animation wouldn’t start instantly and that means a jumpy animation.

There is an easy way to handle that. Move all discrete animations to CompositionTarget.Rendering event handler. This event is fired each time before frame is rendered, giving you the last possibile moment to apply any changes.

 

        public SampleAnimPanel()

        {

            AnimationStoryboard = new Storyboard();

            CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);

        }

        void CompositionTarget_Rendering(object sender, EventArgs e)

        {

            if (_animating && Children != null && Children.Count > 0)

            {

                foreach (var child in this.Children)

                {

                    if (GetElementVisibility(child) == Visibility.Collapsed &&

                        child.Visibility == Visibility.Visible)

                    {

                        child.Visibility = Visibility.Collapsed;

                    }

                    else if (GetElementVisibility(child) == Visibility.Visible &&

                              child.Visibility == Visibility.Collapsed)

                    {

                        child.Visibility = Visibility.Visible;

               }

                }

            }

        } 

 

GetElementVisibility method just checks if X/Y of the object are in the view area

Two questions can arise:

Q: Why not to put all animations, non discrete too, in CompositionTarget.Rendering event handler?

A: Two reasons:

· For non discrete animations it is just easier, more elegant and faster to count end position and start the animation. It is more efficient not only because usually calculating once and approximating progress with each frame is more efficient than calculate with each frame, but also because with the animation engine come several optimizations. For example animating X,Y dependency properties of an object does not means firing binding updates or callbacks attached to it with each generated frame. That is because all bindings are referring to lower priority, non-animated value. More about it here.

· You would miss added value like for example easing effects.

Q: Why not to use for example a timer instead to apply all the logic above?

A: Because correlation with frame rate is really useful and your calculations won’t be fired more or less frequent than necessary.

Comments