Delen via


Layout to layout animations in WCP (part 2)

This post continues my series on layout to layout animations in WCP (Avalon). In part 1, I showed to create a panel that lays out its children in a grid and allows the child size to be controlled with a slider through data binding. In this post, I'll describe one method to animate the children to their new position.

Here's the basic approach. When LayoutOverride needs to move a child, it moves the child, but also applies a RenderTransform to the child to translate it back to its original position. It then applies an animation to the transform so that it decays to zero over time. This has the effect of moving the child to its final position over time. This works even if the item is currently being animated.

In this post, I'll describe the method I showed at PDC. I'll include a couple of alternate approaches in future posts.

In order to implement this approach, we need to figure out the current location of each child before we arrange it so that we can apply the correct transform. This is a bit easier said than done. There's no easy way to get the current location a child in the same coordinate space as the arguments to Arrange(). If you use child.TranslatePoint(), you can get the upper-left corner of the child, but it takes margins and alignment into account. You could try to take those into account yourslef, but that's a very fragile approach. Instead, what we'll do is keep track of the parameters necessary to compute where we positioned the child on last layout, and then apply the current RenderTransform. With the layout algorithm used for TilePanel, we just have to keep track of the child size and children per row to calculate where we put any child.

Here are the modifications to TilePanel. The new code is in bold. I didn't include the methods that did not change.

 // Arrange the children
protected override Size ArrangeOverride(Size finalSize)
{
   // Calculate how many children fit on each row
   int childrenPerRow = Math.Max(1, (int) Math.Floor(finalSize.Width / this.ChildSize));

   for (int i = 0; i < this.Children.Count; i++)
   {
      UIElement child = this.Children[i];

      // Figure out where the child goes
      Point newOffset = CalcChildOffset(i, childrenPerRow, this.ChildSize);

      if (_oldChildrenPerRow != -1)<br>      {<br>         // Figure out where the child is now<br>         Point oldOffset = CalcChildOffset(i, _oldChildrenPerRow, _oldChildSize);<br>         if (child.RenderTransform != null)<br>         {<br>            child.RenderTransform.TransformPoint(oldOffset, out oldOffset);<br>         }<br>         // Transform the child from the new location back to the old position<br>         TranslateTransform childTransform = new TranslateTransform();<br>         child.RenderTransform = childTransform;<br>         // Decay the transformation with an animation<br>         childTransform.BeginAnimation(TranslateTransform.XProperty, MakeAnimation(oldOffset.X - newOffset.X));<br>         childTransform.BeginAnimation(TranslateTransform.YProperty, MakeAnimation(oldOffset.Y - newOffset.Y));<br>      } 



      // Position the child and set its size
      child.Arrange(new Rect(newOffset, new Size(this.ChildSize, this.ChildSize))); 
   }

    _oldChildrenPerRow = childrenPerRow;<br>   _oldChildSize = this.ChildSize; 

   return finalSize;
}

 // Create an animation to decay from start to 0 over .5 seconds<br>private static DoubleAnimation MakeAnimation(double start)<br>{<br>   DoubleAnimation animation = new DoubleAnimation(start, 0d, new Duration(TimeSpan.FromMilliseconds(500)));<br>   animation.AccelerationRatio = 0.2;<br>   return animation;<br>}<br>private double _oldChildSize = 1d;<br>private int _oldChildrenPerRow = -1; 

The changes end up being pretty simple. _oldChildSize and _oldChildrenPerRow are used to cache the old layout parameters, and then it's a pretty simple matter to calculate the current child positions. Then, we apply the transform to this.

This method is not incredibly general. We're taking advantage of the fact that it's easy to calculate the positions of the children based on just a couple of parameters. You also can't apply it to an existing layout. I'll talk about some alternate approaches soon.

Comments

  • Anonymous
    October 04, 2005
    again, works great and is very smooth...

    how do i apply multiple transforms, either sequentially or simultaneously? is there a Matrix to Transform mapping that can be used to do this so that i can apply matrix math ops to my transform classes?

    thanks.
  • Anonymous
    October 05, 2005
    The RenderTransform property is of type Transform. In the sample above, I used a TranslateTransform, but there are also RotateTransform, ScaleTransform, SkewTransform and MatrixTransform classes. You can get the matrix value of any transform with the .Value property, which you can use to combine transforms.
  • Anonymous
    February 22, 2006
    The comment has been removed
  • Anonymous
    February 22, 2006
    Not to sound too much like a fanboy, but Avalon just plain rocks.&amp;nbsp; I’ve spent the last fifteen years...
  • Anonymous
    February 23, 2006
    Ok, it took me a lot longer to get to this, but I finally have a post about a better way to do layout...
  • Anonymous
    May 24, 2006
    Ok, so one of the things I hope to achieve with this blog is to share all of the little gems that I come...
  • Anonymous
    January 06, 2007
    Ok, it took me a lot longer to get to this, but I finally have a post about a better way to do layout