Silverlight how-to: Animate a grid of tiles
December 20, 2007 @ 3:14 pm in Microsoft, Silverlight
An integral part of our proof-of-concept for the Phizzpop design challenge was a grid of tiles that represented a variety of TV shows and Movies. Transitioning from screen to screen we needed to repopulate this grid with a new set of tiles based on search criteria and filters. To improve the effect of repopulating the grid, we wanted to animate each tile as it was moved into its final position. Nothing crazy was desired, just a simple movement from the left, easing into its final position.
To achieve this effect, our initial approach was to hardcode a string of storyboard XAML. This animation was then inserted as a canvas resource and played to animate the tile movement. Programmatically, we would insert into a string of XAML the appropriate X and Y coordinates for each tiles final destination. Here is an example of what our original approach looked like.
1 2 3 4 5 6 7 8 9 10 11 12 13 | xaml += "<Storyboard xmlns=\http://schemas.microsoft.com/client/2007\";
xaml +=" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" x:Name=\"";
xaml += storyboardName + "\" Storyboard.TargetName=\"" + element.Name + "\" > ";
xaml += " <DoubleAnimation Storyboard.TargetProperty=\"(Canvas.Left)\" To=\"" ;
xaml += currentX + "\" Duration=\"0:0:0.25\" />";
xaml += " <DoubleAnimation Storyboard.TargetProperty=\"(Canvas.Top)\" To=\"";
xaml += currentY + "\" Duration=\"0:0:0.25\" />";
xaml += "</Storyboard>";
Storyboard storyboard = (Storyboard)XamlReader.Load(xaml);
_parentCanvas.Resources.Add(storyboard);
storyboard.Begin();
…. |
Even though this approach met our visual need, we felt it was a poor use of XAML and made tweaking the animation of each tile very difficult. Thanks to some great suggestions from a mentor at Microsoft, we came up with a much more flexible approach.
Instead of programmatically loading custom storyboard XAML into the resource collection, we created a single storyboard and added it as a canvas resource within the “tile.xaml“. This approach allowed us to use Blend to visualize the animation and manipulate its parameters until we got the desired easing and visual effect.
If you examine “tile.xaml there are a few things worth explaining. First, each “SplineDoubleKeyFrame” element has been given a “x:Name”. This name will be used to locate the appropriate “SplineDoubleKeyFrame” used to set the tiles destination X and Y “value”. Second, each “DoubleAnimationUsingKeyFrames” element is targeting the “parentCanvas”. In our tile class the top most canvas is named “parentCanvas”. This is significant, because it allows us to animate the entire tile element regardless of the number of visual elements nested within the UserControls XAML.
To facilitate the process of manipulating each “SplineDoubleKeyFrame” we have created the following helper function and placed it within our tile class.
1 2 3 4 5 6 7 8 9 | public void SetSplineXandY(string sbName, string keyFrameXName, double x, string keyFrameYName, double y)
{
Storyboard sb = _parentCanvas.Resources.FindName(sbName) as Storyboard;
SplineDoubleKeyFrame sdkY = sb.Children[0].FindName(keyFrameYName) as SplineDoubleKeyFrame;
sdkY.Value = y;
SplineDoubleKeyFrame sdkX = sb.Children[0].FindName(keyFrameXName) as SplineDoubleKeyFrame;
sdkX.Value = x;
} |
As we populate the grid with tiles, we call “SetSplineXandY” to modify the appropriate X and Y coordinates prior ton playing the storyboard. Here is a snip of that code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | foreach (Tile element in Children)
{
_Iteration++;
if (currentX + element.Width + marginX * 2 > panelWidth)
{
// move to next line
currentX = 0 + marginX;
currentY = nextRowY + marginY;
}
Storyboard sb = element.ParentCanvas.Resources.FindName("TileAnimation") as Storyboard;
//this is moving it immediatly to the correct Y
element.SetValue(Canvas.TopProperty, currentY);
element.SetValue(Canvas.LeftProperty, -150);
sb.Completed += new EventHandler(TransitionIn_Completed);
element.SetSplineXandY("TileAnimation", "keyFrameX", (currentX+150), "keyFrameY", 0.0);
sb.Begin();
_TransitionsInOut.Add(sb);
// calculate offset for the next row
nextRowY = Math.Max(nextRowY, currentY + element.Height + (marginY * 2));
// calculate position for the next element
currentX += element.Width + (marginX * 2);
} |
Since we are executing a series of asynchronous storyboards it is beneficial to have a way to identify that all of the animations have completed. Individually, each storyboard fires a “Completed” event when it is finished. Attaching to this event combined with a collection of executed storyboards references we can determine when all storyboard are done playing.
That’s it. Click here to download a sample project that demonstrates this technique.
Tagged as 
