Posts Tagged with SplineDoubleKeyFrame

Silverlight 3D flip animation

Up until now I have not had an opportunity to create any Silverlight procedural animations that were more complicated then positioning an object with some simple easing. I thought it would be fun to attempt to duplicate one of the transitions used on the iPhone. While making a phone call, if you choose to display the keypad there is a cool 3D flip that occurs. Here is a quick video of the transition. [[VIDEO]] In hopes not to reinvent the wheel, I embarked on an endless search of the Internet for anything resembling a flip. After some quality time on Google, I stumbled upon a great Flash site called reflektions by Paul Ortchanian. This site has a ton of Flash examples that highlight 2D and 3D effects done in flash. Those of us new to interactive development can learn a ton from the techniques Flash and Flex developers have used for years. To get started I created a simple page that contained two Grid controls, one for the front of the flip and one for the back. Both were placed inside of a single canvas named “screens”.

The guts of this effect has been encapsulated in a method called “AnimateFlip”. The result of this method is a DobuleAnimation with approximately 48 descreate keys frames.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
private TimeSpan AnimateFlip(Storyboard sb, ScaleTransform scale, 
out TimeSpan tsLastFrame)
{
    double speed = 4
    double flipRotation = 0
    double flipped = 2
    tsLastFrame = new TimeSpan()
    TimeSpan tsSideFlipped = new TimeSpan()
 
    int frames = 1
    DoubleAnimationUsingKeyFrames daX = 
	new DoubleAnimationUsingKeyFrames()
    tsLastFrame = new TimeSpan()
    while (flipRotation != flipped * 180)
    {
        flipRotation += speed
        double flipRadian = flipRotation * (Math.PI / 180)
        double size = Math.Cos(flipRadian)
        double scalar = (1 / (1 / size))
 
        DiscreteDoubleKeyFrame ddkfX = new DiscreteDoubleKeyFrame()
        ddkfX.Value = (size * scalar) 
 
        tsLastFrame = TimeSpan.FromMilliseconds(frames * 7)
 
        //the first time we flip to negative capture the 
	//tsLastFrame so we know when we will need to
        //visualize the flip side
        flipped = (size < 0) ? +1 : +0
        if (flipped == 1 && tsSideFlipped.Ticks == 0)
        {
            tsSideFlipped = tsLastFrame
        }
 
        ddkfX.KeyTime = KeyTime.FromTimeSpan(tsLastFrame)
 
        //add the DiscreteDoubleKeyFrame to the 
	//DoubleAnimationUsingKeyFrames
        daX.KeyFrames.Add(ddkfX)
 
        flipRotation %= 360
        frames++
    }
 
    Storyboard.SetTarget(daX, scale)
    Storyboard.SetTargetProperty(daX, "ScaleX")
    sb.Children.Add(daX)
 
    VisualizeSide(sb, tsSideFlipped, 0, TimeSpan.FromMilliseconds
		((tsSideFlipped.Milliseconds + 100)), 
		back.Opacity, this.back)
    VisualizeSide(sb, TimeSpan.FromMilliseconds((
		tsSideFlipped.Milliseconds - 100)), front.Opacity, 
		tsSideFlipped, 0, this.front)
    back.Opacity = 0
    return tsLastFrame
}

At the very bottom of the above method their is two calls to “VisualizeSide”. This method is responsible for visualizing the second side when the screen has rotated 180 degrees (Flipped).

My animation is by no means exact, but a pretty good first attempt. If you watch the video very slowly you might notice that on the iPhones rotation it looks like it rotates in perspective. I would love to add this, but am at a lost

Code: ScreenFlip.zip

Silverlight how to: Procedural Animation

Whenever possible developers should encourage designers to maintain all of their Silverlight storyboards within XAML. This allows the designers the optimum environment to use a tool like Blend to tweak the animation timing, easing and any relevant Key Splines. That said, there are situations where a developer may create a UserControl that requires full control over the format of the animation. Defining this storyboard procedurally, allows the developer a more consistent level of control and allows an easier approach to manipulating properties within this storyboard.

The following will show a simple example of how one could include a “DoubleAnimationUsingKeyFrames” that allows the TranslateTransform’s “X” property to be manipulated each time the animation is played.

The first step in defining our animating UserControl is to attach a “Loaded” event handler within our constructor. This allows us to have the XAML parsed by the UserControls base class, yet inject a procedural storyboard into the UserControls resources.

1
2
3
4
5
public Box()
{
  InitializeComponent()
  this.Loaded += new RoutedEventHandler(Page_Loaded)
}

Our storyboard will include a “DoubleAnimationUsingKeyFrames”, “SplineDoubleKeyFrame” with a corresponding “KeySpline”. I have chosen to refactor this logic into a method. In the event that we wanted to manipulate both the “X” and the “Y” TranslateTransform property, this method could be called twice passing both “X” and “Y” as the last argument.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static SplineDoubleKeyFrame CreateDoubleAnimation(ref Storyboard sb,
   TranslateTransform translation, string property)
{
    DoubleAnimationUsingKeyFrames da = new DoubleAnimationUsingKeyFrames()
    da.SetValue(NameProperty, string.Concat("da_", property))
 
    SplineDoubleKeyFrame sdkf = new SplineDoubleKeyFrame()
    sdkf.SetValue(NameProperty, string.Concat("sdkf_",property))
 
    sdkf.Value = 0
    sdkf.KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(0.25))
 
    //define the KeySpline
    KeySpline ksX = new KeySpline()
    ksX.ControlPoint1 = new Point(1.0, 0.25)
    ksX.ControlPoint2 = new Point(0.75, 1.0)
    sdkf.KeySpline = ksX
 
    //add the SplineDoubleKeyFrame to the DoubleAnimationUsingKeyFrames
    da.KeyFrames.Add(sdkf)
 
    //define which TranslateTransform property will be targeted by the DoubleAnimation
    Storyboard.SetTarget(da, translation)
    Storyboard.SetTargetProperty(da, property)
    sb.Children.Add(da)
    return sdkf
}

Within our Page_Loaded handler we define the transformation group for this control, call our helper function and insert the storyboard into the UserControls grid (LayoutRoot) resource collection. In this example, we are only manipulating the 2D, X/Y coordinate system so we only need to include the “TranslateTransform” within the transform group. If Scale was being translated as well, we would include a “ScaleTransform” object within the group.

1
2
3
4
5
6
7
8
9
10
11
12
void Page_Loaded(object sender, RoutedEventArgs e)
{
    //define the Render Transformation for this control
    TranslateTransform translation = new TranslateTransform()
    TransformGroup transforms = new TransformGroup()
    transforms.Children.Add(translation)
    this.RenderTransform = transforms
 
    _sdkfX = CreateDoubleAnimation(ref _sb, translation, "X")
 
    this.LayoutRoot.Resources.Add("playerAnimation", _sb)
}

Lastly, I created a public method in the UserControl that sets the “X” and starts the animation. This method can be called repeatedly from the location that is instantiating this UserControl.

1
2
3
4
5
6
public bool Animate(int x)
{
    _sdkfX.SetValue(SplineDoubleKeyFrame.ValueProperty, x)
    _sb.Begin()
    return true
}

Code: Animation.zip

Silverlight how-to: Animate a grid of tiles

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.