Animate ScaleTransform of a Button using VisualStateManager

2.4k views Asked by At

I created a control template for a button which contains a grid and a content control. What I want to do is the following: when the button is pressed, the scale of the button should animate to 0.5 and when the button leaves the pressed state, the button's scale should animate back to 1.0.

In my current solution the animation to scale = 0.5 works nice, if the button is pressed. But as soon as i release the button, the animation doesn't scale slowly back to scale 1. Instead its immediate at scale 1.

My implementation looks like this:

<Style TargetType="Button" x:Name="MyButtonStyle">
        <Setter Property="Margin" Value="0"/>
        <Setter Property="Padding" Value="0"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="Button">
                    <Grid Background="{TemplateBinding Background}" x:Name="buttonLayoutRoot">
                        <Grid.RenderTransform>
                            <ScaleTransform ScaleX="1" ScaleY="1"/>
                        </Grid.RenderTransform>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualStateGroup.Transitions>
                                    <VisualTransition To="Pressed" GeneratedDuration="0:0:2.5">
                                        <Storyboard>
                                            <DoubleAnimation 
                                            Storyboard.TargetName="buttonLayoutRoot"
                                            Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleX)"
                                            To="0.5" Duration="0:0:2.5"/>
                                            <DoubleAnimation 
                                            Storyboard.TargetName="buttonLayoutRoot"
                                            Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)"
                                            To="0.5" Duration="0:0:2.5"/>
                                        </Storyboard>
                                    </VisualTransition>
                                    <VisualTransition To="Normal" GeneratedDuration="0:0:2.5">
                                        <Storyboard>
                                            <DoubleAnimation 
                                            Storyboard.TargetName="buttonLayoutRoot"
                                            Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleX)"
                                            To="1.0" Duration="0:0:2.5"/>
                                            <DoubleAnimation 
                                            Storyboard.TargetName="buttonLayoutRoot"
                                            Storyboard.TargetProperty="(Grid.RenderTransform).(ScaleTransform.ScaleY)"
                                            To="1.0" Duration="0:0:2.5"/>
                                        </Storyboard>
                                    </VisualTransition>
                                </VisualStateGroup.Transitions>
                                <VisualState x:Name="Normal"/>
                                <VisualState x:Name="MouseOver" />
                                <VisualState x:Name="Pressed"/>
                                <VisualState x:Name="Disabled"/>
                            </VisualStateGroup>

                        </VisualStateManager.VisualStateGroups>
                        <ContentControl
                            x:Name="ButtonContent"
                            Content="{TemplateBinding Content}"
                            ContentTemplate="{TemplateBinding ContentTemplate}"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            Margin="{TemplateBinding Padding}">
                        </ContentControl>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

I also tried to put the animations into the visual states "normal" and "pressed" without any transitions. But the outcome was the same. It just snaps back to scale = 1 when the button isn't pressed anymore.

I'm programming for windows phone 8.0 silverlight.

Hope you guys can help me.

Thanks, Kevin

2

There are 2 answers

0
KS85 On BEST ANSWER

The trick is, to define only the visual states, you are interested in. In this case, you are only interested in "pressed" and "normal", so only define those. Here is an example:

<Button.Template>
            <ControlTemplate TargetType="Button">
                <ContentPresenter x:Name="LayoutRoot" RenderTransformOrigin="0.5 0.5">
                    <ContentPresenter.RenderTransform>
                        <ScaleTransform/>
                    </ContentPresenter.RenderTransform>
                    <ContentPresenter.Foreground>
                        <SolidColorBrush Color="White"/>
                    </ContentPresenter.Foreground>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal">
                                <Storyboard Duration="0:0:0.5">
                                    <DoubleAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(ContentPresenter.RenderTransform).(ScaleTransform.ScaleX)"
                                         To="1" Duration="0:0:0.5"/>
                                    <DoubleAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(ContentPresenter.RenderTransform).(ScaleTransform.ScaleY)"
                                         To="1" Duration="0:0:0.5"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard Duration="0:0:0.5">
                                    <DoubleAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(ContentPresenter.RenderTransform).(ScaleTransform.ScaleX)"
                                         To="0.8" Duration="0:0:0.5"/>
                                    <DoubleAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(ContentPresenter.RenderTransform).(ScaleTransform.ScaleY)"
                                         To="0.8" Duration="0:0:0.5"/>
                                    <ColorAnimation Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(ContentPresenter.Foreground).(SolidColorBrush.Color)"
                                                    To="Red" Duration="0"/>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </ContentPresenter>
            </ControlTemplate>
        </Button.Template>

As soon as the visual state manager changes state, the animations are reset, unless the new state is not declared in your template. F.e. if you define only the "pressed" state, the button will stay at scale 0.8. Only if you also declare "Normal", the animation is reset.

1
Chris W. On

Ok, so looking at your stuff...you have another issue in how you're setting your RenderTransform explicit. Leave that thing open since we're interacting with it dynamically (look at the Grid.RenderTransform on my example). You also need to set a RenderTransformOrigin on your buttonLayoutRoot so it knows what you're snapping back too.

So while I don't have time at the moment to walk you through the entire explanation right at this moment since I've got my own work tasks to be doing, here's a generic button style you can use for reference I made awhile back that does what you want. Notice the differences. I pulled out most of my other custom crap in here so there shouldn't be any resource conflicts or anything, but you should probably notice the differences pretty quick.

<Style x:Key="StandardButtonStyle" TargetType="Button">
    <Setter Property="Background" Value="Red" />
    <Setter Property="Foreground" Value="Blue" />
    <Setter Property="FontWeight" Value="SemiBold"/>
    <Setter Property="HorizontalContentAlignment" Value="Center" />
    <Setter Property="VerticalContentAlignment" Value="Center" />
    <Setter Property="Cursor" Value="Hand"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Grid x:Name="Container"
                      RenderTransformOrigin="0.5,0.5" Cursor="{TemplateBinding Cursor}">
                    <Grid.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform />
                            <SkewTransform />
                            <RotateTransform />
                            <TranslateTransform />
                        </TransformGroup>
                    </Grid.RenderTransform>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualStateGroup.Transitions>
                                <VisualTransition From="MouseOver"
                                                  GeneratedDuration="0:0:0.1"
                                                  To="Pressed">
                                    <VisualTransition.GeneratedEasingFunction>
                                        <ExponentialEase EasingMode="EaseIn" Exponent="-2" />
                                    </VisualTransition.GeneratedEasingFunction>
                                </VisualTransition>
                                <VisualTransition From="Pressed"
                                                  GeneratedDuration="0:0:0.1"
                                                  To="MouseOver">
                                    <VisualTransition.GeneratedEasingFunction>
                                        <ExponentialEase EasingMode="EaseOut" Exponent="0" />
                                    </VisualTransition.GeneratedEasingFunction>
                                </VisualTransition>
                            </VisualStateGroup.Transitions>
                            <VisualState x:Name="Normal" />
                            <VisualState x:Name="MouseOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Duration="00:00:00"
                                                                   Storyboard.TargetName="MouseOverState" Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="Visible"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Duration="00:00:00"
                                                                   Storyboard.TargetName="PressedState" Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="Visible"/>
                                    </ObjectAnimationUsingKeyFrames>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Container" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
                                        <EasingDoubleKeyFrame KeyTime="0:0:0.01" Value="1.05" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="Container" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
                                        <EasingDoubleKeyFrame KeyTime="0:0:0.01" Value="1.05" />
                                    </DoubleAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Duration="00:00:00"
                                                                   Storyboard.TargetName="DisabledState" Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="Visible"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Duration="00:00:00"
                                                                   Storyboard.TargetName="FocusedState" Storyboard.TargetProperty="(UIElement.Visibility)">
                                        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="Visible"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Border x:Name="BaseBackground"
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"/>
                    <Border x:Name="MouseOverState"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Visibility="Collapsed"/>
                    <Border x:Name="PressedState"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Visibility="Collapsed"/>
                    <Border x:Name="FocusedState"
                            Background="{TemplateBinding Background}"
                            Visibility="Collapsed"/>
                    <ContentControl x:Name="contentPresenter"
                                    Margin="{TemplateBinding Padding}"
                                    HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                    Content="{TemplateBinding Content}"
                                    ContentTemplate="{TemplateBinding ContentTemplate}"
                                    FontFamily="{TemplateBinding FontFamily}"
                                    FontSize="{TemplateBinding FontSize}"
                                    FontWeight="{TemplateBinding FontWeight}"
                                    Foreground="{TemplateBinding Foreground}"
                                    IsTabStop="False"/>
                    <Border x:Name="DisabledState"
                            Background="White"
                            Visibility="Collapsed"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Hope this helps, cheers! :)