Анимированное изменение ориентации экрана в приложении Windows Phone

от автора

Легко заметить что в Магазине Windows Phone очень много приложений вообще работающих только в портретной ориентации. Отчасти это объясняется тем, что таково положение вещей по умолчанию в Windows Phone. Образцом же приложения, по максимуму использующему возможность опрокидывания экрана, можно считать стандартный Калькулятор.

В портретной ориентации мы получаем простой калькулятор. А в альбомной уже — инженерный.

Но самое прекрасное — это анимация перехода из одного режима в другой.

Разрабатывая свой небольшой проект: приложение TapHint, реализовал несколько вещей, улучшающих восприятие интерфейса программы. Почему-то мне показалась нетривиальной реализация такой полезной вещи как анимация переворота телефона. И в интернете как-то не без усилий была найдена информация по этой теме. Само приложение работает со стандартными NFC-метками NDEF формата, записывая в них информацию и, соответственно, читая её из них.

Для страницы приложения, показывающей записанную в метку информацию, и решено было применить поддержку анимированных переворачиваний. Результат выглядит так.

Портретный и альбомный вид:

Кадры из анимации промежуточных состояний:

Как оказалось, реализация обработок всех возможных видов опрокидывания телефона получается довольно громоздкой.

XAML страницы ShowPage.xaml

<phone:PhoneApplicationPage     x:Class="Tap_Hint.ShowPage"     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"     xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"     xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"     FontFamily="{StaticResource PhoneFontFamilyNormal}"     FontSize="{StaticResource PhoneFontSizeNormal}"     Foreground="{StaticResource PhoneForegroundBrush}"     SupportedOrientations="PortraitOrLandscape" Orientation="Portrait"     mc:Ignorable="d"     shell:SystemTray.IsVisible="True"     Name="showPage" OrientationChanged="showPage_OrientationChanged">      <phone:PhoneApplicationPage.Projection>         <PlaneProjection x:Name="showPageRotation" CenterOfRotationX="0" RotationY="0"/>     </phone:PhoneApplicationPage.Projection>      <phone:PhoneApplicationPage.Resources>         <Storyboard x:Name="showPageStoryboardTo">             <DoubleAnimation From="-55.0" To="0.0" Duration="00:00:00.35" Storyboard.TargetName="showPageRotation" Storyboard.TargetProperty="RotationY">                 <DoubleAnimation.EasingFunction>                     <CubicEase EasingMode="EaseIn"/>                 </DoubleAnimation.EasingFunction>             </DoubleAnimation>         </Storyboard>     </phone:PhoneApplicationPage.Resources>      <!--LayoutRoot is the root grid where all page content is placed-->     <Grid x:Name="LayoutRoot" Background="Transparent">          <!-- VisualStateManager.VisualStateGroups must be defined in main grid -->         <VisualStateManager.VisualStateGroups>             <VisualStateGroup>                 <VisualState x:Name="FromPToLR"> <!-- from portrait to landscape right and so on -->                     <Storyboard>                         <ObjectAnimationUsingKeyFrames Storyboard.TargetName="showTransform" Storyboard.TargetProperty="TranslateY">                             <DiscreteObjectKeyFrame KeyTime="0" Value="-16"/>                         </ObjectAnimationUsingKeyFrames>                         <DoubleAnimation From="190.0" To="0.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="TranslateX">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                         <DoubleAnimation From="90.0" To="0.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="Rotation">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                     </Storyboard>                 </VisualState>                 <VisualState x:Name="FromPToLL">                     <Storyboard>                         <ObjectAnimationUsingKeyFrames Storyboard.TargetName="showTransform" Storyboard.TargetProperty="TranslateY">                             <DiscreteObjectKeyFrame KeyTime="0" Value="-16"/>                         </ObjectAnimationUsingKeyFrames>                         <DoubleAnimation From="-190.0" To="0.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="TranslateX">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                         <DoubleAnimation From="-90.0" To="0.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="Rotation">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                     </Storyboard>                 </VisualState>                 <VisualState x:Name="FromLRToP">                     <Storyboard>                         <DoubleAnimation From="0.0" To="-190.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="TranslateY">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                         <DoubleAnimation From="-90.0" To="0.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="Rotation">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                     </Storyboard>                 </VisualState>                 <VisualState x:Name="FromLLToP">                     <Storyboard>                         <DoubleAnimation From="0.0" To="-190.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="TranslateY">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                         <DoubleAnimation From="90.0" To="0.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="Rotation">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                     </Storyboard>                 </VisualState>                 <VisualState x:Name="FromLRToLL">                     <Storyboard>                         <ObjectAnimationUsingKeyFrames Storyboard.TargetName="showTransform" Storyboard.TargetProperty="TranslateY">                             <DiscreteObjectKeyFrame KeyTime="0" Value="-16"/>                         </ObjectAnimationUsingKeyFrames>                         <DoubleAnimation From="-180.0" To="0.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="Rotation">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                     </Storyboard>                 </VisualState>                 <VisualState x:Name="FromLLToLR">                     <Storyboard>                         <ObjectAnimationUsingKeyFrames Storyboard.TargetName="showTransform" Storyboard.TargetProperty="TranslateY">                             <DiscreteObjectKeyFrame KeyTime="0" Value="-16"/>                         </ObjectAnimationUsingKeyFrames>                         <DoubleAnimation From="180.0" To="0.0" Duration="00:00:00.50" Storyboard.TargetName="showTransform" Storyboard.TargetProperty="Rotation">                             <DoubleAnimation.EasingFunction>                                 <CubicEase EasingMode="EaseInOut"/>                             </DoubleAnimation.EasingFunction>                         </DoubleAnimation>                     </Storyboard>                 </VisualState>             </VisualStateGroup>         </VisualStateManager.VisualStateGroups>          <Grid.RowDefinitions>             <RowDefinition Height="Auto"/>             <RowDefinition Height="*"/>         </Grid.RowDefinitions>          <!--TitlePanel contains the name of the application and page title-->         <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="0,0,0,0" Orientation="Horizontal">             <Image Width="32" Height="32" Source="/Tap Hint - logo mini (tr, white).png" Margin="5,0,0,0"/>             <TextBlock Canvas.ZIndex="8" Text="{Binding Path=LocalizedResources.AboutPage_ApplicationTitle, Source={StaticResource LocalizedStrings}}" FontSize="22" Margin="5,0,0,0"/>             <!--<TextBlock Name="textBlockPlus" Canvas.ZIndex="8" Text="{Binding Path=LocalizedResources.AboutPage_ApplicationTitle_Plus, Source={StaticResource LocalizedStrings}}" FontSize="20" Margin="5,0,0,0" Foreground="#FF4959FF"/>-->         </StackPanel>          <!--ContentPanel - place additional content here-->         <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">             <Border x:Name="showBorder" Grid.Row="1" Width="400" Height="300" BorderThickness="5" RenderTransformOrigin="0.5,0.5">                 <Border.RenderTransform>                     <CompositeTransform x:Name="showTransform" Rotation="0" TranslateY="-190" TranslateX="0"/>                 </Border.RenderTransform>                 <ScrollViewer x:Name="showScroller" VerticalScrollBarVisibility="Auto">                     <TextBlock x:Name="showTextBlock" TextAlignment="Center" TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Auto"/>                 </ScrollViewer>             </Border>         </Grid>     </Grid> </phone:PhoneApplicationPage> 

Для начала нужно указать с помощью параметра SupportedOrientations=«PortraitOrLandscape», что поддерживаем обе ориентации страницы приложения. Параметр Orientation=«Portrait» задаёт ориентацию по умолчанию. Но это будет лишь опрокидывать страницу туда-сюда без всяких анимаций. Для описания набора анимаций нужно задействовать VisualStateManager. В нём можно описать все переходы-VisualState’ы, которыми хотим воспользоваться в различных ситуациях. В каждом VisualState можно описать Storyboard с анимациями (если их несколько, то они проигрываются одновременно).

Например, VisualState для перехода из портретной ориентации в альбомную правую можно назвать x:Name="FromPToLR". Можно пояснить что «альбомная правая» — это альбомная при положении правой грани телефона внизу. Таким образом получаем одну портретную ориентацию и две альбомные. Ситуация отягчается тем, что необходимо описать переходы от одной ориентации к другой в обоих направлениях. И что может быть неочевидным — переход из одного альбомного положения в другое и обратно. Итого шесть переходов.

CS страницы ShowPage.xaml.cs

using System.Linq; using System.Windows; using System.Windows.Navigation; using System.Windows.Media; using Microsoft.Phone.Controls;  namespace Tap_Hint {     public partial class ShowPage : PhoneApplicationPage     {         private PageOrientation m_ePageOrientation = PageOrientation.PortraitUp;          public ShowPage()         {             InitializeComponent();              bool isDefaultColor = SettingsStore.get().extractParamBool(SettingsStore.StoringParams.USE_DEFAULT_COLOR.ToString());             showTextBlock.FontSize = SettingsStore.g_FontSizeStep * (SettingsStore.get().extractParamInt(SettingsStore.StoringParams.FONT_SIZE.ToString()) + 1);             if (isDefaultColor)             {                 showTextBlock.Foreground = (Brush)Application.Current.Resources["PhoneAccentBrush"];             }             else             {                 showTextBlock.Foreground = new SolidColorBrush(Colors.White);             }         }          protected override void OnNavigatedTo(NavigationEventArgs e)         {             if (e.NavigationMode == NavigationMode.New)             {                 showPageStoryboardTo.Begin();                 if (NavigationContext.QueryString.Values != null && NavigationContext.QueryString.Values.ToArray() != null &&                     NavigationContext.QueryString.Values.ToArray().Length > 0)                 {                     if ("tagParam".Equals(NavigationContext.QueryString.Keys.ElementAt(0)))                     {                         showTextBlock.Text = NavigationContext.QueryString.Values.ElementAt(0);                     }                     if ("isEnc".Equals(NavigationContext.QueryString.Keys.ElementAt(1)) && "true".Equals(NavigationContext.QueryString.Values.ElementAt(1)))                     {                         showBorder.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 250, 250, 0));                     }                     else if ("isEnc".Equals(NavigationContext.QueryString.Keys.ElementAt(1)) && "false".Equals(NavigationContext.QueryString.Values.ElementAt(1)))                     {                         showBorder.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 20, 230, 30));                     }                     else if ("isEnc".Equals(NavigationContext.QueryString.Keys.ElementAt(1)) && "spec".Equals(NavigationContext.QueryString.Values.ElementAt(1)))                     {//spec - error or encoded on enother device                         showBorder.BorderBrush = new SolidColorBrush(Color.FromArgb(255, 240, 30, 30));                     }                 }                 else                 {                     showTextBlock.Text = "no param";                 }             }         }          protected override void OnNavigatingFrom(NavigatingCancelEventArgs e)         {             if (e.NavigationMode != NavigationMode.Back)             {//leave the page due to long back button or Windows button, stay on the page                 return;             }              App.Current.Terminate(); //application exiting         }          private void showPage_OrientationChanged(object sender, OrientationChangedEventArgs e)         {             //playback animations on orientation change             if (m_ePageOrientation == PageOrientation.PortraitUp && e.Orientation == PageOrientation.LandscapeRight)             {                 VisualStateManager.GoToState(this, "FromPToLR", true);             }             else if (m_ePageOrientation == PageOrientation.PortraitUp && e.Orientation == PageOrientation.LandscapeLeft)             {                 VisualStateManager.GoToState(this, "FromPToLL", true);             }             else if (m_ePageOrientation == PageOrientation.LandscapeRight && e.Orientation == PageOrientation.PortraitUp)             {                 VisualStateManager.GoToState(this, "FromLRToP", true);             }             else if (m_ePageOrientation == PageOrientation.LandscapeLeft && e.Orientation == PageOrientation.PortraitUp)             {                 VisualStateManager.GoToState(this, "FromLLToP", true);             }             else if (m_ePageOrientation == PageOrientation.LandscapeRight && e.Orientation == PageOrientation.LandscapeLeft)             {                 VisualStateManager.GoToState(this, "FromLRToLL", true);             }             else if (m_ePageOrientation == PageOrientation.LandscapeLeft && e.Orientation == PageOrientation.LandscapeRight)             {                 VisualStateManager.GoToState(this, "FromLLToLR", true);             }                          //saving current orientation mode             if (e.Orientation == PageOrientation.PortraitUp)             {                 m_ePageOrientation = PageOrientation.PortraitUp;             }             else if (e.Orientation == PageOrientation.LandscapeRight)             {                 VisualStateManager.GoToState(this, "LandscapeState", true);                 m_ePageOrientation = PageOrientation.LandscapeRight;             }             else if (e.Orientation == PageOrientation.LandscapeLeft)             {                 m_ePageOrientation = PageOrientation.LandscapeLeft;             }         }        } } 

В коде страницы остаётся воспользоваться обработчиком изменения ориентации — showPage_OrientationChanged(). Анализируя то какой была ориентация с помощью значения переменной m_ePageOrientation и то какой ориентация экрана стала в e.Orientation, можно применить нужную анимацию.

Остаётся надеяться что будет больше приложений для телефонов, в которых не будут лениться делать эти полезные украшательства.

ссылка на оригинал статьи http://habrahabr.ru/post/272875/