In v2.4, ObjectListView integrated with the Sparkle animation library to allow animations to be drawn onto its controls.
“I’d like to welcome you all to this weeks meeting of Google Enviers’ Anonymous. Hi. My name is Phillip and I’m a Google envier. I’ve managed to control my condition for almost six days. But this morning I fell off the wagon in a big way. I was working normally and then I saw it! You all know what happens next. My breathing was suddenly rapid and shallow. I reached for my paper bag to keep calm. But it was too late. Waves of jealousy swept over me. In desperation, I speed-dialed my Google Envier buddies, Steve and Bill, and they talked me through it. After a minute or so, the spasm passed and I returned to (more or less) normal, though the bitter taste of envy lingered for the rest of the day.
In my self defense, the source of my envy is nothing as crass as their billion dollar development budget, their wonderful working conditions, or their thousand plus dollar share price. All these things fall into insignificance in comparison with the true object of my obsession: their animations! The way they effortlessly add little spinning stars, glowing text or fading sparkles to their applications. My applications sit there fully functional and obedient – but static and passive, lacking the moving eye candy that make Google apps cute and cool.
But no more! To you, fellow enviers, I present the Sparkle animation framework. With this framework, you too can put animations into your applications and free yourself from the shackles of Google envy.”
OK. A little more seriously this time. The Sparkle library’s purpose is to allow (almost) any Control to show animations.
The design goals of the Sparkle library are:
To use the library itself, you’ll need to grasp its four major concepts:
Adding any sort of animation to an application is a multi-step process. With Sparkle, the workflow for creating an animation is:
To get a feeling for how to use the library, there’s no substitute for seeing code. So let’s put a spinning, fading star in the middle of an ObjectListView.
To put animations onto an ObjectListView, you use AnimatedDecoration class. Depending on the constructor used, this will create an animation for the whole list, for a row, or for just one cell:
AnimatedDecoration listAnimation = new AnimatedDecoration(this.olvSimple); AnimatedDecoration rowAnimation = new AnimatedDecoration(this.olvSimple, myModel); AnimatedDecoration cellAnimation = new AnimatedDecoration(this.olvSimple, myModel, olvColumnName);
We want an animation that can draw on the whole list so we use this form:
AnimatedDecoration listAnimation = new AnimatedDecoration(this.olvSimple); Animation animation = listAnimation.Animation;
Now we have an Animation, we can make our Sprites. We only need one sprite and we want it to show an image, so we use an ImageSprite. There is also a TextSprite for drawing bordered/backgrounded text, and ShapeSprite for drawing regular shapes:
Sprite image = new ImageSprite(Resource1.largestar);
We have our sprite. Now we want it to do something. Whenever we want a sprite to do something, we need an Effect. For this example, we want the image to spin in the centre of the list, and to fade out while it does so. We add the Effects to the Sprite, saying when the effect should start and how long it will last:
image.Add(0, 2000, Effects.Rotate(0, 360 * 2.0f)); image.Add(1000, 1000, Effects.Fade(1.0f, 0.0f));
This says, during the first 2000 milliseconds after the sprite begins in the animation, the image should spin completely twice. The second statement says that 1000 milliseconds after the sprite begins, the sprite should take 1000 milliseconds to gradually fade completely from sight.
Most Sprites have some sort of MoveEffect given to them to move them around. However, we want this sprite to just stay in the centre of the list, so we give it a FixedLocation:
image.FixedLocation = Locators.SpriteAligned(Corner.MiddleCenter);
This says the images MiddleCenter will always be aligned to the MiddleCenter of the animation (which in this case is on the whole of the list).
The final steps are to add the Sprite to the Animation:
This says to begin running the image sprite 0 milliseconds after the animation starts (i.e. immediately). Often the sprites would not start until some time after the animation begins, but in this case, there’s only one sprite so it may as well start immediately.
The animation is now fully configured and all that remains is to run it:
All being well, this should produce an animation on the ObjectListView that looks something like this:
In eight lines of code, you’ve put a spinning, fading star (a la Picasa) onto your otherwise static ListView.
An animation has two distinct functions.
In addition to animating sprites and rendering them, an animation supports the basic set of commands to control its execution:
Animations have a Repeat property, which controls the animation’s behaviour when it reaches the end of the animation.
Sprite are the actual eye candy – the pretty do-nothing things that the user can see. They keep whatever state information they require – location, size, color, transparency – and then use that state information to draw themselves when asked. They don’t change their own state – that’s the responsibility of Effects.
There are several flavours of sprites that come with the Sparkle library:
This takes an Image and draws it according to the sprites state. If the given Image is a frame animation itself, the Sparkle framework will animate that image automatically. I think it is only animated GIFs that Microsoft supports as frame animations.
TextSprites draw text (no prizes). But they can do a bit more formatting than just that. The text can be colored (ForeColor property), they can be drawn with a background (BackColor property). They can draw a border around the text (BorderWidth and BorderColor properties). The border can be either a rectangle (set CornerRounding to 0) or a round cornered rectangle (set CornerRounding to greater than 0 – 16 is normally nice).
These draw regular shapes (square, rectangles, round cornered rectangle, triangles, ellipses/circles). Like TextSprites, ShapeSprites can have a ForeColor (color of frame of the shape), BackColor (used for the filled part of the shape), and PenWidth (width of the frame).
Remember, all colors can have alpha values set for them, which will allow varying levels of transparency when drawing the sprites.
Effects are the movers and shakers of the Sparkle library. They push Sprites around, moving them here or there, making them visible or invisible, spinning them around. Any time you want a Sprite to change, you need an Effect.
Effects are given to a Sprite, and told when they should start and how long they will run for:
this.imageSprite.Add(100, 250, new FadeEffect(0.0f, 0.8f));
This says, “100 milliseconds after imageSprite starts in the animation, this FadeEffect should, during 250 milliseconds, fade the sprite from hidden (0.0 opacity) to 80% visible (0.8 opacity).”
Many Effects work by “tweening” - they are given an initial value and a target value, and as the effect progresses, the effect gradually change a property on their Sprite from the initial value to the end value. In the above example, the FadeEffect’s initial value is 0.0 and its end value is 0.8. As the animation progresses, the FadeEffect would gradually change the Opacity property of its Sprite from 0.0 to 0.8. So, 100 milliseconds after the sprite starts, the imageSprite will be hidden; after 225 milliseconds, it will be 40% visible; after 350 milliseconds, it will be 80% visible, and then the effect will stop.
Effects factory contains static methods to create many commonly used effects.
Move the sprite from it’s current location to a corner of the animation.
Move the sprite from one corner of the animation to another. This has a zillion variations which allow different ways of saying where to start and where to end.
Go to (as in Monopoly) the given corner without any transition.
Change the Opacity of the sprite from the start to the end value, effectively fading it in or out.
Change the Spin of the sprite from the start to the end value (both in degrees).
Change the Scale of the sprite, effectively making it bigger or smaller.
Change the Bounds of the sprite.
This is the first interesting effect. This changes the location of the sprite so that it “walks” around the perimeter of the given rectangle. This has several flavours saying which exact point of the sprite will be walked, which direction the walk should take, and where the walking should start.
Another interesting effect. This changes the Opacity of the sprite so that it blinks a number of times. There are a couple of variations that allow the characteristics of the “blink” to be changed: how long it takes to fade in, stay visible, fade out, stay invisible.
This applies the given Effect several times to the Sprite.
In some ways, locators are the most difficult concept to grasp. If you can get this concept, everything else normally falls into place.
A Locator is a point or a rectangle that can calculate itself whenever needed. A plain Point is fixed, but a PointLocator can be different every time it is called. By using a Locator, “how” a point is calculate can be replaced at runtime to use any strategy it likes.
For example, the MoveEffect changes the Location of a Sprite. It could be coded to move a Sprite to the TopLeft of an Animation:
this.Sprite.Location = this.Animation.Bounds.Location;
This is nice and obvious solution, but not very flexible. If we then wanted to move the sprite to the centre of the Animation, we’d have to write a separate line of code, and then give some way to choose which line to execute. And another line of code for ever other possible location we could want.
But with Locators, the MoveEffect simply says:
this.Sprite.Location = this.Locator.GetPoint();
By using this extra layer of abstraction, the intelligence of calculating the “where” is placed into a separate object, and becomes reusable from there.
Locators is a factory that has static methods to produce many common locators. You can of course create the locators directly – these are just a convenience.
Create a PointLocator for a fixed point.
Create a PointLocator which is where a Sprite must be moved to so that the given Corner is located at the corresponding corner of the Animation. So, Locators.SpriteAligned(Corner.BottomRight) calculates where a sprite must be moved to so that its BottomRight corner is at the BottomRight corner of the Animation.
Same as above, but the point is offset by the given fixed amount.
Create a PointLocator which is where a Sprite must be moved to so that the given Corner is located at a point proportional across and down the bounds the Animation. So, Locators.SpriteAligned(Corner.BottomRight, 0.6f, 07.7) calculates where a sprite must be moved to so that its BottomRight corner is 60% across the Animation and 70% down.
Create a PointLocator which calculates the given corner of the sprite’s bounds.
Create a PointLocator which calculates a given proportion across and down the sprite’s bounds.
Create a RectangleLocator for a fixed rectangle.
Create a RectangleLocator for the bounds of the animation.
Create a RectangleLocator for the bounds of the animation inset by the given amount
Create a RectangleLocator for the bounds of the sprite.
Create a RectangleLocator for the bounds of the sprite inset by the given amount
The Sparkle library performs fairly well when used in accordance with its design goals. Animating dozens of sprites with dozens of effects has a minimal impact on performance. On my laptop, 20 or so sprites with a variety of effects uses only about 2-3% of the CPU. The limiting factor is not the animation but the redrawing of the underlying control. Currently, the whole control is redrawn every frame. For simple controls, like Buttons or UserControls, this is not a problem, but for complicated control, like DataGridView, this redrawing quickly becomes taxing.
Later versions might be optimized by invalidating only the smallest possible area of the control.
I haven’t tried using Sparkle with thousands of sprites. That really wasn’t its purpose.
Sparkle is a new library. It has worked well for me, but I’m sure there are bugs in it. Please report them and I will fix them.
The interfaces and major classes are stable, but not yet fixed (unchangeable). It’s possible that I will add a few more properties to the ISprite interface (I think it needs Skew).