Silverlight/WPF FlipImage Animation

4 minute read

I was working on some Silverlight samples and needed an image that could flip over.

All the samples I could find on the net were pretty complex and contained a lot of code to do the animation and I wanted something really simple.

To create the illusion of an image that flips over (or any kind of UI element that flips over) you just need 4 things

  1. A front image / UI element
  2. A back image / UI element
  3. A flip animation
  4. A reverse animation

The flip and reverse animations can be very simple as you will see below.

In order to make it reusable in our application we can create a UserControl and call it FlipImage

First create 2 UI elements (front and back), in this case Grids but they could really be any kind of items, like images, data grids etc.

<Grid x:Name="LayoutRoot">
    <Grid x:Name="front">
        <Border Background="AntiqueWhite"  BorderBrush="DarkGray" BorderThickness="3" CornerRadius="10" />
        <Image x:Name="imgFront" Stretch="Fill" Height="100" Width="100" />
    </Grid>
    <Grid x:Name="back" >
        <Border Background="AliceBlue" BorderBrush="DarkGray" BorderThickness="3" CornerRadius="10" />
        <Image x:Name="imgBack" Stretch="Fill" Height="100" Width="100" />
    </Grid>
</Grid>

The animation for flipping the image is very simple. First we make the back image/UI element zero size, then to do the flip animation we just shrink the front image towards the middle, and when it is shrunk to zero size we expand the back image.

To get it to shrink around the middle we need to set the RenderTransformOrigin to 0.5,0.5… and in order to be able to do the shrinking/expanding we need to add <ScaleTransform...> to the UI Elements that we can target in the animations.

With all this, our XAML now looks like this

<Grid x:Name="LayoutRoot">
    <Grid x:Name="front" RenderTransformOrigin="0.5,0.5">
        <Grid.RenderTransform>
            <ScaleTransform/>
        </Grid.RenderTransform>
        <Border Background="AntiqueWhite"  BorderBrush="DarkGray" BorderThickness="3" CornerRadius="10" />
        <Image x:Name="imgFront" Stretch="Fill" Height="100" Width="100" />
    </Grid>
    <Grid x:Name="back" RenderTransformOrigin="0.5,0.5">
        <Grid.RenderTransform>
            <ScaleTransform ScaleX="0"/>
        </Grid.RenderTransform>
        <Border Background="AliceBlue" BorderBrush="DarkGray" BorderThickness="3" CornerRadius="10" />
        <Image x:Name="imgBack" Stretch="Fill" Height="100" Width="100" />
    </Grid>
</Grid>

Now we can add the storyboard to the UserControl.Resources to do the flip animation

<UserControl.Resources>
    <Storyboard x:Name="sbFlip">
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="front"  Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.2" Value="0"/>
        </DoubleAnimationUsingKeyFrames>
        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00.2" Storyboard.TargetName="back" Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleX)">
            <SplineDoubleKeyFrame KeyTime="00:00:00.4" Value="1"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</UserControl.Resources>

There are two animations here… the first one targets front, and goes from 00:00:00 to 00:00:00.2 (200 milliseconds), and in that time it will ScaleX from the current value down to 0.

The second one goes from 00:00:00.2 to 00:00:00.4 (also 2 milliseconds) and will expand back by scaling x wise from the current value to 1 (full size).

sbReverse looks exactly the same, except for that front and back are reversed.

In order to be able to configure the front image, back image and to be able to call Flip and Reverse from the app we can add the following methods and properties for the class

Note: You need to add a using statement for System.Windows.Media.Imaging for the BitmapImage

public partial class FlipImage : UserControl
{
    public bool Reversed = false;

    public string FrontImage
    {
        set
        {
            imgFront.Source = new BitmapImage(new Uri(value, UriKind.Relative));
        }
        get
        {
            return imgFront.Source.ToString();
        }
    }

    public string BackImage
    {
        set
        {
            imgBack.Source = new BitmapImage(new Uri(value, UriKind.Relative));
        }
        get
        {
            return imgBack.Source.ToString();
        }
    }

    public FlipImage()
    {
        InitializeComponent();
        sbFlip.Completed += new EventHandler(sbFlip_Completed);
        sbReverse.Completed += new EventHandler(sbReverse_Completed);
    }

    void sbReverse_Completed(object sender, EventArgs e)
    {
        Reversed = false;
    }

    void sbFlip_Completed(object sender, EventArgs e)
    {
        Reversed = true;
    }

    public void Flip()
    {
        if (!Reversed)
        {
            sbFlip.Begin();
        }
    }

    public void Reverse()
    {
        if (Reversed)
        {
            sbReverse.Begin();
        }
    }
}

You can add the images to our application like this

<StackPanel x:Name="LayoutRoot" Orientation="Horizontal" Background="White">       
    <my:FlipImage x:Name="Home" Tag="http://blogs.msdn.com/tess/default.aspx" FrontImage="Images/Home.png" BackImage="Images/Description.png" RenderTransformOrigin="0.5,0.5" Margin="10,0,0,0">
        <my:FlipImage.RenderTransform>
            <RotateTransform Angle="5"/>
        </my:FlipImage.RenderTransform>
    </my:FlipImage>
    <my:FlipImage x:Name="Contact" Tag="http://blogs.msdn.com/tess/contact.aspx" FrontImage="Images/Contact.png" BackImage="Images/Description.png" RenderTransformOrigin="0.5,0.5">
        <my:FlipImage.RenderTransform>
            <RotateTransform Angle="-3"/>
        </my:FlipImage.RenderTransform>
    </my:FlipImage>
...

Note: In order to be able to use <my:... you need to add a reference to your assembly in the user control definition, eg. xmlns:my="clr-namespace:FlipMenu"

I added a rotate transform to make the menu look pretty, and a Tag that we can use in the mouseleftbuttondown to navigate to the link. The tag is just a property of any control that can be used to store any data you want related to the control.

And the code for this menu page is then extremely simple… we just flip on mouseenter, reverse on mouseleave and navigate on mouseleftbutton down:

Public Page()
{
    InitializeComponent();
    Info.MouseEnter += new MouseEventHandler(MenuMouseEnter);
    Contact.MouseEnter += new MouseEventHandler(MenuMouseEnter);
    Home.MouseEnter +=new MouseEventHandler(MenuMouseEnter);

    Home.MouseLeave += new MouseEventHandler(MenuMouseLeave);
    Info.MouseLeave += new MouseEventHandler(MenuMouseLeave);
    Contact.MouseLeave += new MouseEventHandler(MenuMouseLeave);

    Home.MouseLeftButtonDown += new MouseButtonEventHandler(MenuMouseLeftButtonDown);
    Info.MouseLeftButtonDown += new MouseButtonEventHandler(MenuMouseLeftButtonDown);
    Contact.MouseLeftButtonDown += new MouseButtonEventHandler(MenuMouseLeftButtonDown);
}

void MenuMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    FlipImage mi = sender as FlipImage;
    HtmlPage.Window.Navigate(new Uri(mi.Tag.ToString()));
}

void MenuMouseLeave(object sender, MouseEventArgs e)
{
    FlipImage mi = sender as FlipImage;
    if(mi.Reversed)
        mi.Reverse();
}

void MenuMouseEnter(object sender, MouseEventArgs e)
{
    FlipImage mi = sender as FlipImage;
    if(!mi.Reversed)
        mi.Flip();
}

And that is all there is to it…

Until next time, Tess