MenuFlyout flip animation on Windows Phone WinRT

MenuFlyout flip animation on Windows Phone WinRT

MenuFlyouts are a standard part of Windows Phone apps. If you go to system apps such as Calendar and try to switch view by clicking the button in the Command/AppBar, you'll notice that the MenuFlyout shows and sort of flips. However, if you try to show a MenuFlyout in your app, you'll see that it has a different entrance animation.

The system animation looks like this:

System MenuFlyout animation

Imagine you wanted to show a MenuFlyout when CommandBar button was clicked. CommandBar is simply defined in Page.BottomAppBar.

<Page.BottomAppBar>  
    <CommandBar x:Name="AppBar">
        <AppBarButton x:Name="WeekButton" Click="WeekButton_Click" Label="Date">
            <AppBarButton.Icon>
                <SymbolIcon Symbol="Calendar" />
            </AppBarButton.Icon>
        </AppBarButton>
    </CommandBar>
</Page.BottomAppBar>  

And you can open it like this:

private void WeekButton_Click(object sender, RoutedEventArgs e)  
{
    MenuFlyout mf = (MenuFlyout)this.Resources["DateFlyout"];
    mf.Placement = FlyoutPlacementMode.Top;
    mf.ShowAt(this.AppBar);
}

However, the default style in that case is this:

Default MenuFlyout animation

How do I change the Style?

The MenuFlyout has a standard Style which is set for TargetType="MenuFlyoutPresenter" and can be found in ..\Program Files (x86)\Windows Phone Kits\8.1\Include\abi\Xaml\Design\generic.xaml. (Style shortened here in blog post)

<Style TargetType="MenuFlyoutPresenter">  
    // property defaults
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="MenuFlyoutPresenter">
          <Border x:Name="OuterBorder" FlowDirection="LeftToRight" BorderBrush="{TemplateBinding BorderBrush}">
            <VisualStateManager.VisualStateGroups>
              <VisualStateGroup x:Name="PlacementStates">
                <VisualState x:Name="None" />
                <VisualState x:Name="TopPortrait">
                  <Storyboard>
                    <PointAnimation Storyboard.TargetName="InnerBorder" Storyboard.TargetProperty="RenderTransformOrigin" Duration="0:0:0" To="0,1" />
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="OuterBorder" Storyboard.TargetProperty="BorderThickness">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="0,0,0,1.5" />
                      <DiscreteObjectKeyFrame KeyTime="0:0:0.50" Value="{ThemeResource MenuFlyoutPortraitBorderThemeThickness}" />
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="CenterBorder" Storyboard.TargetProperty="BorderThickness">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="0,0,0,1.5" />
                      <DiscreteObjectKeyFrame KeyTime="0:0:0.50" Value="{ThemeResource MenuFlyoutPortraitBorderThemeThickness}" />
                    </ObjectAnimationUsingKeyFrames>
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ItemsPresenter" Storyboard.TargetProperty="Margin">
                      <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MenuFlyoutPortraitThemePadding}" />
                    </ObjectAnimationUsingKeyFrames>
                    <DoubleAnimation Storyboard.TargetName="OuterScaleTransform" Storyboard.TargetProperty="ScaleX" Duration="0:0:0.38" From="0.0" To="1.0" />
                    <DoubleAnimationUsingKeyFrames Storyboard.TargetName="InnerScaleTransform" Storyboard.TargetProperty="ScaleY">
                      <LinearDoubleKeyFrame KeyTime="0:0:0" Value="0.0" />
                      <LinearDoubleKeyFrame KeyTime="0:0:0.38" Value="0.0" />
                      <LinearDoubleKeyFrame KeyTime="0:0:0.50" Value="1.0" />
                    </DoubleAnimationUsingKeyFrames>
                  </Storyboard>
                </VisualState>
                <VisualState x:Name="BottomPortrait">
                  // Bottom placement Storyboard
                </VisualState>
                <VisualState x:Name="LeftLandscape">
                  // Left placement Storyboard
                </VisualState>
                <VisualState x:Name="RightLandscape">
                   // Right placement Storyboard
                </VisualState>
              </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
            <Border.RenderTransform>
              <ScaleTransform x:Name="OuterScaleTransform" />
            </Border.RenderTransform>
            <Border x:Name="CenterBorder" FlowDirection="LeftToRight" BorderBrush="{TemplateBinding Background}">
              <StackPanel x:Name="InnerBorder" FlowDirection="{TemplateBinding FlowDirection}" Background="{TemplateBinding Background}">
                <StackPanel.RenderTransform>
                  <ScaleTransform x:Name="InnerScaleTransform" />
                </StackPanel.RenderTransform>
                <ItemsPresenter x:Name="ItemsPresenter" Margin="{TemplateBinding Padding}" FlowDirection="{TemplateBinding FlowDirection}" />
              </StackPanel>
            </Border>
          </Border>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>

This Style defines a ControlTemplate with a couple of VisualStates which contain animations.
Notice the four placement states - TopPortrait, BottomPortrait, LeftLandscape, RightLandscape.

Let's change how the MenuFlyout acts when placed on top. Start by copying the whole Style to your resources. Then you need to find the TopPortrait VisualState and clear everything from the Storyboard to be able to define your own from scratch.

In order to get the MenuFlyout to flip, you can use a PlaneProjection class - it'll give you that sort of a 3D effect which is what you're looking for. I added it to the CenterBorder Border element and set the default value to -90. Setting it to -90 means that it's perpendicular to the screen and the MenuFlyout is therefore not visible when first shown.

// ... rest of the code
<Border x:Name="CenterBorder" FlowDirection="LeftToRight" BorderBrush="{TemplateBinding Background}">  
    <Border.Projection>
        <PlaneProjection RotationX="-90"/>
    </Border.Projection>
// ... rest of the code

Flipping the MenuFlyout means you just need to rotate it around X axis.

// ... rest of the code
<VisualState x:Name="TopPortrait">  
    <Storyboard>
        <DoubleAnimation Duration="0:0:0.18" 
                         To="0" 
                         Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationX)" 
                         Storyboard.TargetName="CenterBorder" />
     </Storyboard>
</VisualState>  
// ... rest of the code

And that's it!

Changed MenuFlyout animation

You can do similar things for different placement modes. Hope this helps!

EDIT: This article focuses on the flipping animation. If you're interested in also hiding the AppBar like the Calendar app does, I just found out about this great article by Tim Gabrhel that you should read: Flyout on Windows Phone 8.1 Runtime

Igor Ralic

igor ralic

View Comments
Microsoft Certified Solutions Developer: Windows Store Apps in C#