Мобильные программы

Навигация в Windows Phone. Часть 2: Более сложные задачи

Йочай Кириати

Загрузка примера кода

[Это завершает первый выпуск новой ежемесячной рубрики журнала MSDN Magazine*, Мобильные программы. С ростом значимости мобильных технологий, например, Windows Phone 7, мы будем регулярно публиковать практические советы экспертов, предназначенные для разработчиков всех уровней, которые хотят улучшить свои навыки в этой критически важной технологической области. Мы просим вас помочь в создании новой рублики по мере того, как мы будем над ней работать. Пишите нам по адресу mmeditor@microsoft.com, чтобы предложить темы, поделиться идеями или задать вопросы. Мы рады вашим отзывам.—Ред.*]

В прошлый раз мы познакомились с моделью навигации в Windows Phone и соответствующими API (см. msdn.microsoft.com/magazine/gg650662). Теперь мы поделимся приемами для выполнения более сложных задач навигации. Будут рассмотрены следующие аспекты Windows Phone.

  • В навигационных API вы можете использовать методы Navigate и GoBack. Эти два метода либо записывают, либо считывают URL в истории навигации [стек переходов назад (back stack)]. Поскольку для управления стеком переходов назад никаких API не предусмотрено, вы должны самостоятельно управлять переходными экранами (transient screens). Такие экраны — это диалоги или запросы, которые пользователи не должны посещать повторно при нажатии кнопки Back, например диалог входа.
  • Windows Phone поддерживает единовременно только одну операцию навигации. Если вам нужна функция «домой», которая обычно является сокращением для пользователя, желающего сделать обратный переход через несколько страниц, то нужно последовательно вызывать метод GoBack (один вызов единовременно) требуемое число раз; это может повлиять на производительность UI.
  • Ни один из двух участников навигации — PhoneApplicationPage и PhoneApplicationFrame — не имеет готовой поддержки транзитных страниц.

Давайте проанализируем эти аспекты.

Переходной контент

Переходными являются экраны, которые не следует добавлять в стек переходов назад, например диалог входа. Пользователь переходит с какой-либо страницы в диалог входа, но, когда он переходит вперед и нажимает кнопку Back, он должен попадать не обратно в диалог входа, а на предыдущий экран.

Существует несколько способов создания и отображения переходных экранов. Самый очевидный — использовать Silverlight Popup для вывода UI. Однако в подходе с применением Popup есть две небольшие проблемы.

  1. Popup не распознает ориентацию экрана. Это не столь серьезная проблема — вы могли бы написать Popup с поддержкой ориентации экрана или просто вставить Popup в визуальное дерево, а затем выполнять разметку в соответствии с ориентацией страницы.
  2. Для контента внутри полноэкранного Popup не действует аппаратное ускорение. В случае UI диалога входа (и многих других переходных страниц) этого и не требуется, но представьте ситуацию, где в Popup содержится ListBox с достаточно большим количеством элементов, которые нужно прокручивать, или анимированный ProgressBar: здесь потребуется аппаратное ускорение Popup для повышения «отзывчивости» UI-потока.

Из-за этих мелких проблем мы рекомендуем вставлять соответствующий контент в визуальное дерево с более высоким индексом z-порядка, чем для контента текущей страницы. Вот пошаговый рецепт.

  1. Упакуйте контент, который вы хотите встроить в визуальное дерево, в элемент управления User, чтобы его можно было вставить в это дерево и показывать на вашей странице (без смешивания с UI страницы).
  2. В PhoneApplicationPage, который будет показывать UI, обрабатывайте обратный вызов OnBackKeyPress или событие BackKeyPress для пропуска переходного UI (без навигации обратно к нему) при нажатии кнопки Back.
    1. Собственные элементы управления, имеющие несколько состояний (в раскрытом и свернутом виде), должны предоставлять свойства, чтобы хост-страница могла запрашивать их состояние и определять, способны ли они в текущем состоянии воспринимать нажатие кнопки Back (если применимо). Например, элемент ListPicker в инструментальном наборе элементов управления имеет свойство ListPickerMode, элемент ContextMenu — свойство IsOpen и т. д.
  3. Скрывайте ApplicationBar на странице, когда отображаете переходной UI (если применимо). ApplicationBar рисуется операционной системой, и, когда его свойство Opacity равно Opaque (1.0), он резервирует пространство на вашей странице под областью контента. Чтобы имитировать свой переходной UI как новый экран, вы скорее всего захотите скрыть панель приложения.
  4. Анимируйте переходной UI при его выводе и удалении для согласованности с остальной частью UI вашего приложения (если применимо).

Просмотр кода: Чтобы увидеть переходной UI, посмотрите TransientUISample.xaml.cs в исходном коде, который можно скачать для этой статьи; там же вы найдете комментарии с пошаговыми инструкциями.

Большая часть перечисленных требований должна быть понятна на интуитивном уровне; единственное, что нуждается в пояснении, — п. 2a. Для соответствия требованиям сертификации PhoneApplicationPage, отображающий переходный экран, не должен разрешать возврат на предыдущую страницу, пока показывается переходной UI. Вместо этого он должен убирать переходной UI.

В первой части этой статьи мы упоминали, что для предотвращения переходов по нажатию аппаратной кнопки Back вы должны обрабатывать OnBackKeyPress или событие BackKeyPress. Если на вашей странице содержится элемент управления с двумя состояниями и одно из них является переходным, то потребуется какая-то координация между этими страницей и элементом управления. Чтобы реализовать согласование, мы рекомендуем следующее.

  1. Собственные элементы управления с несколькими состояниями (например, ListPicker) должны подписываться на событие BackKeyPress в своей хост-странице и соответственно обрабатывать его. Если они находятся в переходном состоянии, то должны отменять нажатие кнопки Back и удалять свое переходное состояние.
    1. Подписывать эти элементы управления на данное событие следует, только когда это необходимо. Откладывайте подписку на событие BackKeyPress до того момента, когда это реально потребуется.
    2. Мы предпочитаем, чтобы элементы управления «проходили» вверх по визуальному дереву при поиске своего PhoneApplicationPage, но мы видели, как эксперты (например, группа разработчиков инструментального набора элементов управления) делают это обращением к Application RootVisual (известному нам как PhoneApplicationFrame) и просмотром его содержимого. Это, конечно, более производительный и все равно безопасный способ, так как в приложениях Silverlight может быть только один UI-поток (а значит, RootVisual для вашего элемента управления на странице не может измениться, когда программа находится в обработчике события).
  2. Собственные элементы управления и переходные UI должны предоставлять свойства, чтобы хост-страница могла запрашивать их состояние и определять, способны ли они в текущем состоянии воспринимать нажатие кнопки Back (если применимо). Например, элемент ListPicker в инструментальном наборе элементов управления имеет свойство ListPickerMode, элемент ContextMenu — свойство IsOpen и т. д.   
  3. Ваш главный PhoneApplicationPage должен распознавать любые переходные элементы управления или UI и выполнять проверку на их наличие перед тем, как осуществлять любые переходы в OnBackKeyPress. Помните: OnBackKeyPress вызывается до вызова любых обработчиков события BackKeyPress. Если вы полагаетесь на то, что свое переходное состояние будут обрабатывать сами элементы управления, убедитесь, что они не вызывают метод Navigate до вызова обработчиков.

Навигация по кнопке Home (и очистка стека переходов назад)

Распространенный шаблон для мобильных платформ — наличие кнопки Home, которая ускоряет навигацию и отправляет на конкретную страницу. API навигации в Windows Phone не обрабатывают этот шаблон напрямую; фактически в руководствах настоятельно не рекомендуется создавать кнопку Home. Гораздо предпочтительнее иметь тщательно спроектированную, относительно линейную структуру навигации. Если ваша структура навигации не требует более двух-трех уровней переходов, то, вероятно, удобнее просто несколько раз нажать аппаратную кнопку Back, чем тратить вычислительные ресурсы на жест поиска кнопки Home на экране и щелкать ее. Но, если несмотря на все сказанное, вы решили создать кнопку Home, то при реализации навигации вы должны принять во внимание несколько соображений.

  • Помните, что Windows Phone поддерживает один переход за раз. То есть, если у вас четыре уровня переходов в структуре навигации, вам придется последовательно вызывать GoBack три раза, чтобы попасть на начальную страницу («домой»). Поскольку параллельные переходы не поддерживаются, вы должны ждать, пока не завершится текущий переход (когда срабатывает событие Navigated), и только потом начинать следующий переход. Эта «последовательность переходов» может вызывать короткие задержки, если на загрузку вашей страницы требуется немалое время, или мерцание, если ваша страница видна, пока вы «раскручиваете» стек переходов назад.
  • Анимации для ApplicationBar реализуются самой операционной системой. А значит, если вы совершаете переходы между страницами в цикле, ваш UI опять же может мигать из-за анимации ApplicationBar, показываемого на страницах в цикле.

Рецепт решения проблемы навигации с кнопкой Home выглядит следующим образом. (Соответствующий код реализован через класс BackNavigationHelper, который можно скачать в комплекте с другим кодом для этой статьи.)

  1. Скрывайте PhoneApplicationFrame (RootVisual для приложения) на экране, пока вы раскручиваете стек переходов назад. Это избавит от мерцания UI (он мигнет лишь раз).
    1. Если загрузка ваших страниц требует слишком много времени, вы можете вывести полноэкранный Popup с анимацией на это время, чтобы пользователь знал, что происходит.
    2. Вы также должны устанавливать флаг до начала навигации «домой», чтобы ваши страницы не выполняли лишнюю работу в своих обратных вызовах OnNavigatedTo. При навигации назад эти страницы будут выгружаться сразу после завершения перехода (поэтому любые операции в UI будут отбрасываться).
  2. Скрывайте ApplicationBar (устанавливая его свойство IsVisible в false) на каждой странице, которые прокручиваются назад (если применимо).
    1. Заметьте, что это требуется только для страниц, где ApplicationBar на 100% непрозрачный (его Opacity = 1.0).
  3. Раскручивайте стек.
    1. Вызывайте метод GoBack.
    2. Слушайте событие Navigated (в RootVisual ) и вновь вызывайте GoBack, если вы еще не попали на «домашнюю» страницу.

Сбросьте все обратно в нормальное состояние, когда вы закончите раскрутку стека.

Просмотр кода: Вы можете увидеть примеры навигации с возвратом «домой» на страницах в папке HomeNavigationSamplePages в комплекте исходного кода, который можно скачать для этой статьи. Интересный код, конечно, находится в классе BackNavigationHelper, но в нашем примере есть страница BackNavConfig, где вы можете выбрать нужные варианты в BackNavigationHelper (такие как скрытие рамки, скрытие панели приложения, проверка журнала истории переходов и т. д.).

Переходы страниц

Последняя и самая сложная концепция навигации, которая может вас заинтересовать, связана с переходами страниц (page transitions). В контексте навигации такой переход можно рассматривать как имитацию перелистывания страниц вручную. Навигация по-прежнему происходит, и используется нижележащая инфраструктура навигации, которую мы обсуждали ранее, но, когда начинается процесс навигации, содержимое PhoneApplicationPages, участвующего в навигации, анимируется так, чтобы имитировать перелистывание.

Рецепт переходов сложнее. По сути здесь, как и для лазаньи, которую готовит ваша бабушка, нет единого рецепта — реализация таких переходов потребует «тонкой настройки» в соответствии с вашими вкусами (и потребностями приложения). Теперь, когда мы вас предупредили, обсудим несколько принципов, которым вы должны следовать в реализации, и, конечно, дадим пример.

Основные принципы заключаются в следующем.

  1. Соблюдайте все принципы, описанные ранее для кнопки Back. Эффект перехода — просто расширение навигации, а не оправдание отказа в сертификации.
  2. Тщательно продумайте контекст и цель анимации перехода. В Windows Phone имеется отличный набор эффектов перехода, но большая часть из них предназначена для определенного контекста или применения. Вы должны понимать цель анимации, чтобы реализовать правильные переходы в своем приложении. Джефф Арнольд (Jeff Arnold) (ведущий проектировщик анимаций для Windows Phone) сделал короткую запись всех анимаций и переходов; вы обязательно должны посмотреть ее, чтобы понять назначение каждой анимации. Этот видеоролик находится по ссылке bit.ly/eBTkjD.
  3. Стремитесь к тому, чтобы ваши переходы были быстрыми и короткими.
    1. Помните, что переход — это и выход с текущей страницы, и вход на другую страницу. Эти две фазы складываются и поэтому должны быть короткими. Общее время в 300 мс, на наш взгляд, это верхний предел.
    2. Откладывайте на время перехода как можно больше работы, связанной с UI. Если на вашей странице можно обойтись без связывания с данными или дорогостоящих операций разметки, подумайте, как это сделать. Вы можете выполнить анимацию перехода экрана, а затем быстро перейти к его заполнению.

Сводное описание анимаций дано в табл. 1.

Табл. 1 Сводное описание анимаций

Анимация Использование программы Направления Замечания по переходу
Turnstile Переключает пользователя из одного пространства в другое (по умолчанию в рамках устройства). Предназначена для выражения происходящего перехода ForwardIn, ForwardOut, BackwardIn и BackwardOut Это часто используемая анимация. Весьма проста в применении
Continuum Пользователь переходит из одного пространства в другое, но получает впечатление непрерывности. Контекст переносится из одного пространства в другое. Возникает ощущение, будто вы никуда не переходили In, Out Распространенная анимация, но более сложная в реализации. Требует переноса контекста (имитация того, что UIElement переносится между двумя страницами)
Swivel Используется для переходных UI, например для диалогов. Отличается от других анимаций тем, что переход не выполняется, а пользователь остается в том же пространстве (или по крайней мере создается такое впечатление) ForwardIn, ForwardOut, FullScreenIn, FullScreenOut, BackwardIn, BackwardOut Для эффектов перехода используется нечасто — в основном для диалогов
Slide Используется для переходных UI. Переносит контент через существующий контекст SlideUpFadeIn, SlideUpFadeOut, SlideDownFadeIn, SlideDownFadeOut, SlideLeftFadeIn, SlideLeftFadeOut, SlideRightFadeIn, SlideRightFadeOut Часто применяется для эффектов перехода в переходных UI
Rotate Поворачивает экран в заданном направлении и на указанный угол. In90Clockwise, In90CounterClockwise, In180Clockwise, In180CounterClockwise, Out90Clockwise, Out90CounterClockwise, Out180Clockwise, Out180CounterClockwise Для эффектов перехода используется нечасто — в основном для ориентации

Учитывая три перечисленных ранее принципа, можно приступить к кодированию переходов страниц. Windows Phone Controls Toolkit поддерживает такие переходы, и в нем есть объекты Storyboard для большинства распространенных переходов. 

Заметьте: чтобы вместе с нами следовать оставшейся части статьи, вам понадобится Silverlight for Windows Phone Toolkit (silverlight.codeplex.com). Код в этой статье написан применительно к февральскому выпуску (и должен работать в более поздних выпусках).

Для переходов в Toolkit используется TransitionFrame, производный от PhoneApplicationFrame, но имеет свой шаблон с двумя презентаторами контента (тогда как в PhoneApplicationFrame только один презентатор контента). TransitionFrame прослушивает изменения в собственном свойстве Content и обеспечивает появление нового Content (страницы) и стирание старого контента. 

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

Табл. 2. Четыре возможных перехода страницы

Переход (имя свойства) Description (Описание)
NavigationInTransition.Forward Вызывается, когда вы переходите на эту страницу методом Navigate (в направлении вперед)
NavigationInTransition.Backward Вызывается, когда пользователь инициирует переход на эту страницу при навигации в направлении назад
NavigationOutTransition.Forward Вызывается, когда вы уходите с этой страницы методом Navigate (в направлении вперед)
NavigationOutTransition.Backward Вызывается, когда вы уходите с этой страницы методом Navigate (в направлении назад)

Эти эффекты переходов являются экземплярами расширяемого класса NavigationTransition. В Toolkit включены пять встроенных NavigationTransition: TurnstileTranstion, SlideTransition, SwivelTransition, RotateTransition и RollTransition. Все они тоже являются расширяемыми.

А теперь пошаговые инструкции для реализации эффектов переходов с использованием Toolkit:

1. Скачайте Silverlight Control Toolkit и добавьте на него ссылку.

2. Замените рамку RootVisual для своего приложения на TransitionFrame, отредактировав файл App.xaml.cs (рис. 3).

3. Применяйте свойства эффекта перехода к страницам (рис. 2).

Рис. 3. Замена рамки RootVisual для приложения редактированием файла App.xaml.cs

private void InitializePhoneApplication()
  {
    if (phoneApplicationInitialized)
        return;

    // Create the frame but don't set it as RootVisual yet; this allows the splash
    // screen to remain active until the application is ready to render.
    RootFrame = new Microsoft.Phone.Controls.TransitionFrame();
    RootFrame.Navigated += CompleteInitializePhoneApplication;

    // Handle navigation failures
    RootFrame.NavigationFailed += RootFrame_NavigationFailed;

    // Ensure we don't initialize again
    phoneApplicationInitialized = true;
  }

Рис. 4. Применение свойств эффекта перехода к страницам

<phone:PhoneApplicationPage 
    x:Class="LWP.TransitionSamples.Turnstile"
    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="Portrait" Orientation="Portrait"
    mc:Ignorable="d" d:DesignHeight="768" d:DesignWidth="480"
    shell:SystemTray.IsVisible="True"  xmlns:toolkit=
    "clr-namespace:Microsoft.Phone.Controls;assembly=
    Microsoft.Phone.Controls.Toolkit"
  >

  <toolkit:TransitionService.NavigationInTransition>
    <toolkit:NavigationInTransition>
      <toolkit:NavigationInTransition.Backward>
        <toolkit:TurnstileTransition Mode="BackwardIn"/>
      </toolkit:NavigationInTransition.Backward>
      <toolkit:NavigationInTransition.Forward>
        <toolkit:TurnstileTransition Mode="ForwardIn"/>
      </toolkit:NavigationInTransition.Forward>
    </toolkit:NavigationInTransition>
  </toolkit:TransitionService.NavigationInTransition>
  <toolkit:TransitionService.NavigationOutTransition>
    <toolkit:NavigationOutTransition>
      <toolkit:NavigationOutTransition.Backward>
        <toolkit:TurnstileTransition Mode="BackwardOut"/>
      </toolkit:NavigationOutTransition.Backward>
      <toolkit:NavigationOutTransition.Forward>
        <toolkit:TurnstileTransition Mode="ForwardOut"/>
      </toolkit:NavigationOutTransition.Forward>
    </toolkit:NavigationOutTransition>
  </toolkit:TransitionService.NavigationOutTransition>

Заключение

Приложения Windows Phone Silverlight имеют модель навигации в веб-стиле, в соответствии с которой вы переходите с одной страницы на другую и располагаете историей переходов, благодаря чему есть возможность вернуться на предыдущие страницы. Модель навигации легко применять, и она относительно полная. Существует несколько более сложных задач навигации (например, переходные UI, эффекты переходов или функциональность «возврата домой»), которые нужно реализовать самостоятельно. Однако в этой статье из двух частей были изложены соображения по проектированию, которые вы должны учитывать при решении этих более сложных задач навигации. Кроме того, был дан ряд осуществимых рецептов их реализации.

Йочай Кириати (Yochay Kiriaty) — старший технический идеолог в Microsoft; основное внимание уделяет таким клиентским технологиям, как Windows и Windows Phone. Соавтор книг «Introducing Windows 7 for Developers» (Microsoft Press, 2009) и «Learning Windows Phone Programming» (O’Reilly Media, 2011).

Джэйми Родригез (Jaime Rodriguez) — ведущий идеолог в Microsoft в области внедрения новых клиентских технологий, в том числе Silverlight и Windows Phone. Вы можете связаться с ним через twitter.com/jaimerodriguez or on blogs.msdn.com/b/jaimer.

Выражаю благодарность за рецензирование статьи эксперту Питеру Торру (Peter Torr)