次の方法で共有


Silverlight/WPF FlipImage Animation

I was working on some Silverlight samples and needed an image that could flip over as in the example below.

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, datagrids 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 shrinked 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 filp 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();
        }
    }
}

If we want to create a menu similar to the one they use on the Microsoft Italy Student page, you can add the images to our application like this

 

<StackPanel x:Name="LayoutRoot" Orientation="Horizontal" Background="White">       
    <my:FlipImage x:Name="Home" Tag="https://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="https://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>
...

(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")

The result looks something like this:

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...

Btw, you need to check out Nikhils Silverlight.FX project with lots of UI goodies.  He has a control in there that does the flip shown above.

Until next time,
Tess

Comments

  • Anonymous
    March 15, 2009
    PingBack from http://www.clickandsolve.com/?p=23570

  • Anonymous
    March 16, 2009
    I was working on some Silverlight samples and needed an image that could flip over as in the example

  • Anonymous
    March 16, 2009
    The comment has been removed

  • Anonymous
    March 16, 2009
    There's a small hiccup. The 'Reversed' is set to true only when the Flip is completed, so if the mouse leaves the element before the animation is completed than it's stays fliped. I guess setting the 'Reversed' to true on the started event might fix it. Thanks for the example.

  • Anonymous
    March 16, 2009
    Good post, Tess. For those doing WPF, you can also check out Josh Smith's Thriple project (http://thriple.codeplex.com/) which does a similar thing, but in 3D.

  • Anonymous
    March 16, 2009
    Hi, Please do a mouse click (left button) and drag on those 3 images that you have drawn above and see the result. It will show your blog home page instead of those image. Thanks.

  • Anonymous
    March 16, 2009
    Hi, Please do a mouse click (left button) and drag on those 3 images that you have drawn above and see the result. It will show your blog home page instead of those images. Thanks.

  • Anonymous
    March 16, 2009
    <nit> 0.2 seconds is 200 milliseconds, not 2. </nit>

  • Anonymous
    March 16, 2009
    "sbFlip.Begin(); " This is the big problem we have with silverlight.  WHY can you not trigger animations via bound properties?   According to the visual blend/design guru/etc mantra, the developer should NEVER know NOR care what the UI is doing or looking like. WHat if the designer removes that animation later?  Or adds three more animations? PROPER bound property trigger support (like WPF claims to have but doesn't actually work) would make this useful, but how is it a good thing to be tying 100% UI animations to  code? I won't say who I work for, but we had a MS UCD evangelist come speak to us for a full day.  He went on and on about how great XAML is because it lets your designers hand a base UI to developers to use, then refine it and add all the "bling" later.  While being able to change STYLES without "develper" intervention is nice, animations only being triggered by code is useless for the model MS themselves are advocating.

  • Anonymous
    March 16, 2009
    Thorntod,   True... changed Mandeep, thats the idea... normally you would refresh the whole page but since this is an embedded iframe it looks a little funky in the sample... And Aleho, yepp about the hickup, not exactly sure how to fix it.  The main point was to show the flip though...

  • Anonymous
    March 16, 2009
    You are voted (great) - Trackback from Web Development Community

  • Anonymous
    March 18, 2009
    Or you could have just looked at this =] http://www.mydotnetplayground.nl/menu/default.aspx

  • Anonymous
    March 18, 2009
    There's no such word as towards  ;-)

  • Anonymous
    March 18, 2009
    > not exactly sure how to fix it Add a second bool to remember the "wanted state". Then check the "current state" against the "wanted state" in the completion handler, and immediately re-start the animation is they don't match.

  • Anonymous
    March 18, 2009
    InteXX, there is now:)  just invented it

  • Anonymous
    May 01, 2009
    Can you flip more than 2 UI Elements?

  • Anonymous
    January 06, 2010
    I just want to know, when it flips if there is another control it rotate behind that control i tries Xindex but no luck do yu hsve any Idea I tried zindex and other methods but doesn't work

  • Anonymous
    January 09, 2010
    Hi. good guide! But you forgot one thing;) Silverlight can run in your browser. And if you put your mouse away quickly without the browser will not flip back. This is done by checking where the mouse is on screen. This should perhaps attach?. :)

  • Anonymous
    June 15, 2010
    Easy and straightforward tutorial. This tutorial fix image and page flipping issue that I've been struggling with for the past two weeks. sbFlip.Completed += new EventHandler(sbFlip_Completed); failed to work for me since sbFlip object was not visible in the code behind so I replaced it with: Storyboard sbMyFlippedObject; sbMyFlippedObject= (Storyboard)rc.FindResource("sbFlip"); sbMyFlippedObject.Completed += new EventHandler(sbFlip_Completed); Thank you for sharing this tutorial.