I had the need a while back for a spinny loading animation thingy – they seem to be the industry standard “ajaxy load” animation of choice, so I figured I would wack one in my Silverlight application.
I had a bit of a hunt around and to my astonishment there weren’t many around. My searches returned a couple if simple spinny animations where the entire thing spins (i.e. a rotation transform… but I wanted one where each element lights up in turn.
So I got to thinking (which can be dangerous). I decided to jump in to my latest playground – Expression Design – and see what I could come up with. I started with an ellipse object. This would form the “wheel” that the animation would follow. Next I used the text along path feature to draw a series of pipe (“|”) characters around the circle. With some font size and baseline adjustments I had the pipes lining up in to a nice little circle. See the .design file in the download sample. It’s really easy!
Next I exported the design as XAML (with the option to convert text to paths).
In Blend, I pulled out the original Ellipse (the wheel that the pipes are one) as it’s no longer needed, leaving some nicely arranged pipe characters. My loading animation foundation was laid.
The hard part would be actually animating the pipes… or would it.
Naturally I looked to my Animation Chainer library to help out. With a few lines of code I had the animation going.
Basically I walk through each pipe and get its opacity to animate from 100% to 30%. Then the animation waits a bit before starting again (so the other animations around the circle complete). The animation then repeats… forever 🙂
AnimationChainManager am = new AnimationChainManager(false);
int delay = 0;
foreach (UIElement child in SpinWheel.Children)
.DoubleAnimationK() // Create a new Double animation with keyframes
.Target(child) // Target the UIElement
.Property(Path.OpacityProperty) // Change the opacity of the object.
.KeyFrame(1, new TimeSpan(0))// At time 0, have opacity of 100%
.KeyFrame(.3, new TimeSpan(0, 0, 0, 1, 0)) // Move to 30% at 1 second
.KeyFrame(.3, new TimeSpan(0,0,0,0,700)) // Wait 700 ms before repeating (this is how long it takes to get back around
.Repeat() // Keep doing it forever
delay += 100; // wait 100ms in between each animation, so they start slightly after each other (create the wheel effect)
I’ve added a couple of new options to the chainer fluent interface.
Ability to repeat
If tests. Basically they take a lambda… if the lambda returns true it will run the following commands. .EndIf clears out the if test.
I’ve not used the if tests in this project, it was just a muck around that I’ve decided to leave in for next time 🙂
Once the animations have been configured once they look after themselves. Just repeat as needed, animating in sequence around the wheel.
Keep in mind that even when hidden these animations will still run in the background – so put the animation in to a user control and add and remove it from the visual tree as you need to show and hide the animation.
I was thinking about how the night would go when I had the thought that it would be cool to have something to put on the projector to create some atmosphere whilst people are getting seated etc.
The idea I came up with was to take the SDDN logo and make some snow float around it. With the Animation Chainer under my arm and some spare time on the plane (about an hour) I got to work… turned out the job was easier than I thought.
The actual snow randomisation and animation code turned out to be about 10 lines!
The basic premise is that the code loops through 200 times and creates random sized Ellipse objects (the snow). It then applies a random animation to each Ellipse.
When the animation completes it automatically calls back in the animation creation method to kick off the next animation.
The length of each animation is randomised to create a more natural looking snow field… also this serves to ensure that there is no delay/freeze whilst Silverlight is processing the end animations.
void reDoAnims(AnimationConfig g, object context)
// the main guts of the animation... each time one animation finishes, this code is called by the callback.
Ellipse tt = context as Ellipse;
double maxWidth = this.ActualWidth;
double maxHeight = this.ActualHeight;
double moveTop = (double)randNo.Next(0, Convert.ToInt32(maxHeight));
double moveLeft = (double)randNo.Next(0, Convert.ToInt32(maxWidth));
int keyTime = randNo.Next(10, 20);
.KeyFrame(moveLeft, new TimeSpan(0, 0, keyTime))
.KeyFrame(moveTop, new TimeSpan(0, 0, keyTime))
.TimelineCompleteAction(reDoAnims) // when this animation finishes call back this method to reinitialise the object.
Walking through the code from m.Add() on…
Create the animation group
Add a new double animation with keyframes
Set the target to the Ellipse (from the passed on Context)
Target the Ellipse’s Canvas.LeftProperty for animation
Add a keyframe… Animate Canvas.LeftProperty to “moveLeft” over “keyTime” length. Both values are randomised
Queue this animation and ready for another one
Create another double animation with keyframes
Target the Canvas.TopProperty this time
Once again, use the randomised values to animate
Set the context of the animation to the Ellipse. When the callback is called the Ellipse will be the context so the animation can be re-applied (with new random values)
Set the callback so when this animation completes another one is automatically created
Queue the animation
Begin the animations!
Download the code and enjoy!
Leading on from this my brother Alex and I created a little Christmas card for the clients of Readify.
For the last couple of weeks I’ve been mucking around with dynamic animations in Silverlight 2, and I found them quite tiring and verbose to write. I also found it time consuming to link them together in to a series – to run animation a, then b, then c and so on.
So I decided to do something about it, and this is the result.
I created a new set of classes collectively called the AnimationChainer. It allows you to quickly create animations using a convenient syntax. You will find creating animations with this set of classes is much nicer than the manual way. These animations can then be easily chained together to create more advanced behavior.
The basic overview of the AnimationChainer is:
Create groups of animations
Add groups to a main animation controller
Groups contain multiple animations
Animations can be delayed
Groups can run in serial or parallel mode (run all animations at once etc)
You can make the manager run all groups at once or again, serially (so run a group, which has 3 animations, then run the next group and so on)
You can fire callbacks on completion
After a manager has started animating, you can control behavior if you add more animations to it.
Unlike some other animation systems out there already in Silverlight 2, this one uses real Timeline based animations and storyboards. A group becomes a storyboard basically, and the animations are added to it (each one can be cumulatively delayed by more and more).
Note: Press F5 in between each demo to reset the app to the original state:)
The demo has four examples in it:
The first example demonstrates the ability to cut off an executing animation and start a new one. This happens whenever the user moves the mouse. It also shows delayed start for the groups, each letter starts slightly after the one before it.
The second example throws the Windows Vista sample images in to random positions and random rotations on the screen. It does this 10 times for each set of images. This demonstrates serial animations in groups – only one image per group comes out at a time, but parallel groups, i.e. 10 groups send out one image each at a time.
The third example is a wrap panel which uses keyframe animations to “hop” in content.
The fourth example is the same wrap panel, but this time the animations “pop” in. Animated wrap panels are cool! 🙂
These demonstrate only the tip of the iceburg on what is possible with this class.
I don’t get groups and stuff
So what can you do with this? You could make it so that things first zoom in to view, then they might wiggle a little, then zoom out. The Zoom in would be a ScaleY and ScaleX animation, so you would put these in the same group so they would run together. You would put the second part, the wiggle in to another group so you could make it run only after the first animation had completed.
Coding against this class couldn’t be easier.
To create the zoom in animation I spoke of you could do this:
//Set the transform group (you don't have to do this if you element comes from XAML and already has a TransformGroup)
TransformGroup tg = new TransformGroup();
element.RenderTransform = tg;
//Add a scale transform, and set it to scale X and Y to 0
ScaleTransform st = new ScaleTransform();
//Create the chainer manager to house the animation groups
AnimationChainManager cm2 = new AnimationChainManager(true); //true here means that each group will run in serial mode, one after the other.
cm2.Add() //Adds a new animation group
.DoubleAnimation() //Adds a new DoubleAnimation to the group
.Target(st) //Sets the animation target to the ScaleTransform
.Property(ScaleTransform.ScaleXProperty) //Sets the Target Dependency Property
.Duration(new TimeSpan(0, 0, 0, 0, 100)) //Sets the time span of the animation
.From(0) //Sets the starting position of the animation
.To(1) //Sets the ending position of the animation
.Queue() //Adds this animation to the queue (not required, but best practice)
.DoubleAnimation() //Creates a second animation for the other scale axis
.Property(ScaleTransform.ScaleYProperty) //Sets the Target Dependency Property. Note we didn't have to set the Target again!
.From(0) //Sets the starting point and so on.
cm2.Begin(false, false); //Start animations with parameters: Don't wait for previous anims to finish, and don't cancel any that are already running
See, easy as! Watch the video to see this in action (download and play with the sample project too).
Supported animation types
The classes support DoubleAnimation, ColorAnimation and PointAnimation. It also supports the key frame based variants of these three types.
List of available commands
.Serial – Run the animations within the group serially
.Target – The DependencyObject to animate against
.Property – The DependencyProperty to animation
.DoubleAnimation – New DoubleAnimation
.DoubleAnimationK – New DoubleAnimationUsingKeyFrames
.PointAnimation – New PointAnimation
.PointAnimationK – New PointAnimationUsingKeyFrames
.ColorAnimation – New ColorAnimation
.ColorAnimationK – New ColorAnimationUsingKeyFrames
.KeyFrame – Adds a keyframe to the KeyFrame animation. The offset is cumulative (added on to the last offset)
.Context – Some context to fire back with the completion event and callback
.From – From value for all animation types
.To – To value for all animation types
.Duration – The length of this animation
.Offset – The offest from the start of the group to start this animation
.CompleteAction – Action callback
.Reverse – Replay the animation backwards on completion
.Queue – Adds the animation to the queue
When adding multiple animations to the same group, you don’t need to keep setting properties like Target and Duration – the system remembers them and does it automatically for you.
The manager can be configured to behave in different ways.
Play groups sequentially (altered in constructor)
Toggle cancel groups current playing and start newly added groups
Toggle wait for current groups to finish before playing newly added groups
Begin method starts playing the groups
GroupDelay method allows you to set a group to wait before animating.
How does it work
Basically this set of classes is just a wrapper for the animation classes. Each group is converted in to a StoryBoard when it’s told to run 🙂 Download the code and check it out for yourself.
Another example – keyframes
This sample is from the main project available for download. It makes the elements of the wrap panel jump on to the screen.
.DoubleAnimationK() //Create the keyframe animation
.Target(tt) //Set its target to the TranslateTransform created earlier
.Property(TranslateTransform.YProperty) //Tell it to do the YProperty
.KeyFrame(-height, new TimeSpan(0, 0, 0, 0, 1)) //Add first keyframe
.KeyFrame(-300, new TimeSpan(0, 0, 0, 0, 100)) //Add the second keyframe at 101ms
.KeyFrame(0, new TimeSpan(0, 0, 0, 0, 100)) //Add third keyframe, at 201ms (1 + 100 + 100)
.Queue() //Add to queue
.DoubleAnimationK() //Repeat for other axis
.KeyFrame(-(width + realLeft), new TimeSpan(0, 0, 0, 0, 0))
.KeyFrame(-300,new TimeSpan(0, 0, 0, 0, 100))
.KeyFrame(0,new TimeSpan(0, 0, 0, 0, 100))