Иерархическая навигация

Класс NavigationPage обеспечивает иерархическую навигацию, при которой пользователь может переходить по страницам вперед и назад по своему желанию. Этот класс реализует навигацию на основе стека объектов Page по методу ЛИФО (последним поступил — первым обслужен). В этой статье показано, как использовать класс NavigationPage для навигации в стеке страниц.

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

Помещение страницы в стек навигации

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

Извлечение страницы из стека навигации

Методы навигации предоставляются свойством Navigation любых типов, производных от класса Page. Эти методы предоставляют возможность для отправки страниц в стек навигации, извлечения страниц из стека навигации, а также для выполнения операций со стеком.

Выполнение навигации

При иерархической навигации класс NavigationPage используется для перехода по стеку объектов ContentPage. На следующих снимках экрана показаны основные компоненты NavigationPage на каждой платформе.

Компоненты NavigationPage

Макет NavigationPage зависит от платформы:

  • В iOS панель навигации находится в верхней части страницы, где отображается заголовок и кнопка Назад, которая возвращает на предыдущую страницу.
  • В Android панель навигации находится в верхней части страницы, где отображается заголовок, значок и кнопка Назад, которая возвращает на предыдущую страницу. Значок определяется в атрибуте [Activity], который оформляет класс MainActivity в проекте, зависящем от платформы Android.
  • На универсальной платформе Windows панель навигации расположена в верхней части страницы, где отображается заголовок.

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

Примечание.

Рекомендуется заполнять NavigationPage только экземплярами ContentPage.

Создание корневой страницы

Первая страница, добавленная в стек навигации, называется корневой страницей приложения, что демонстрируется в следующем примере кода:

public App ()
{
  MainPage = new NavigationPage (new Page1Xaml ());
}

В результате в стек навигации помещается экземпляр Page1XamlContentPage, где он становится активной страницей и корневой страницей приложения. Эти действия показаны на следующих снимках экрана:

Корневая страница стека навигации

Примечание.

Свойство RootPage экземпляра NavigationPage предоставляет доступ к первой странице в стеке навигации.

Помещение страниц в стек навигации

Для перехода к странице Page2Xaml необходимо вызвать метод PushAsync свойства Navigation текущей страницы, как показано в следующем примере кода.

async void OnNextPageButtonClicked (object sender, EventArgs e)
{
  await Navigation.PushAsync (new Page2Xaml ());
}

В результате в стек навигации помещается экземпляр Page2Xaml, где он становится активной страницей. Эти действия показаны на следующих снимках экрана:

Страница, помещенная в стек навигации

Когда вызывается метод PushAsync, происходят следующие события.

  • У страницы, вызывающей PushAsync, вызывается переопределение OnDisappearing.
  • Вызывается переопределение OnAppearing страницы, к которой осуществляется переход.
  • Задача PushAsync завершается.

Однако точный порядок, в котором происходят эти события, зависит от платформы. Дополнительные сведения см. в главе 24 книги о Xamarin.Forms Чарльза Петцольда (Charles Petzold).

Примечание.

Вызовы переопределений OnDisappearing и OnAppearing не могут рассматриваться как гарантия перехода на страницу. Например, в iOS переопределение OnDisappearing вызывается на активной странице, когда приложение завершает работу.

Извлечение страниц из стека навигации

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

Чтобы вернуться на исходную страницу программным образом, экземпляр Page2Xaml должен вызвать метод PopAsync, как показано в следующем примере кода:

async void OnPreviousPageButtonClicked (object sender, EventArgs e)
{
  await Navigation.PopAsync ();
}

В результате экземпляр Page2Xaml удаляется из стека навигации, а активной становится верхняя страница в нем. Когда вызывается метод PopAsync, происходят следующие события.

  • У страницы, вызывающей PopAsync, вызывается переопределение OnDisappearing.
  • У страницы, к которой вы возвращаетесь, вызывается переопределение OnAppearing.
  • Возвращается задача PopAsync.

Однако точный порядок, в котором происходят эти события, зависит от платформы. Дополнительные сведения см. в главе 24 книги о Xamarin.Forms Чарльза Петцольда (Charles Petzold).

Как и методы PushAsync и PopAsync, свойство Navigation каждой страницы также предоставляет метод PopToRootAsync, который показан в следующем примере кода.

async void OnRootPageButtonClicked (object sender, EventArgs e)
{
  await Navigation.PopToRootAsync ();
}

Этот метод извлекает все, кроме корневого объекта Page, из стека навигации; таким образом, корневая страница приложения становится активной страницей.

Анимация переходов по страницам

Свойство Navigation каждой страницы также предоставляет переопределенные методы отправки и извлечения, которые включают параметр boolean, указывающий, нужно ли отображать анимацию страниц во время перехода, как показано в следующем примере кода.

async void OnNextPageButtonClicked (object sender, EventArgs e)
{
  // Page appearance not animated
  await Navigation.PushAsync (new Page2Xaml (), false);
}

async void OnPreviousPageButtonClicked (object sender, EventArgs e)
{
  // Page appearance not animated
  await Navigation.PopAsync (false);
}

async void OnRootPageButtonClicked (object sender, EventArgs e)
{
  // Page appearance not animated
  await Navigation.PopToRootAsync (false);
}

Установка для параметра boolean значения false отключает анимацию перехода страницы, а установка для параметра значения true включает анимацию, при условии что она поддерживается используемой платформой. Однако методы отправки и извлечения без этого параметра включают анимацию по умолчанию.

Передача данных при переходе

Иногда странице необходимо передать данные другой странице во время навигации. Существует два способа: передача данных с помощью конструктора страниц и указание данных для объекта BindingContext новой страницы. Мы обсудим оба способа.

Передача данных через конструктор страниц

Самый простой способ передачи данных на другую страницу во время навигации — в качестве параметра конструктора страниц, как показано в следующем примере кода:

public App ()
{
  MainPage = new NavigationPage (new MainPage (DateTime.Now.ToString ("u")));
}

Этот код создает экземпляр MainPage, передавая текущую дату и время в формате ISO8601, который упаковывается в экземпляр NavigationPage.

Экземпляр MainPage получает данные с помощью параметра конструктора, как показано в следующем примере кода.

public MainPage (string date)
{
  InitializeComponent ();
  dateLabel.Text = date;
}

Данные отображаются на странице путем установки свойства Label.Text, как показано на следующих снимках экрана.

Данные, переданные через конструктор страниц

Передача данных через объект BindingContext

Альтернативный способ передачи данных на другую страницу во время навигации —указание данных для объекта BindingContext новой страницы, как показано в следующем примере кода:

async void OnNavigateButtonClicked (object sender, EventArgs e)
{
  var contact = new Contact {
    Name = "Jane Doe",
    Age = 30,
    Occupation = "Developer",
    Country = "USA"
  };

  var secondPage = new SecondPage ();
  secondPage.BindingContext = contact;
  await Navigation.PushAsync (secondPage);
}

Этот код задает объекту BindingContext экземпляра SecondPage экземпляр Contact, а затем переходит к SecondPage.

Затем SecondPage использует привязку данных для отображения данных экземпляра Contact, как показано в следующем примере кода XAML.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="PassingData.SecondPage"
             Title="Second Page">
    <ContentPage.Content>
        <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
            <StackLayout Orientation="Horizontal">
                <Label Text="Name:" HorizontalOptions="FillAndExpand" />
                <Label Text="{Binding Name}" FontSize="Medium" FontAttributes="Bold" />
            </StackLayout>
            ...
            <Button x:Name="navigateButton" Text="Previous Page" Clicked="OnNavigateButtonClicked" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

В следующем примере кода показано, как можно выполнить привязку данных в C#.

public class SecondPageCS : ContentPage
{
  public SecondPageCS ()
  {
    var nameLabel = new Label {
      FontSize = Device.GetNamedSize (NamedSize.Medium, typeof(Label)),
      FontAttributes = FontAttributes.Bold
    };
    nameLabel.SetBinding (Label.TextProperty, "Name");
    ...
    var navigateButton = new Button { Text = "Previous Page" };
    navigateButton.Clicked += OnNavigateButtonClicked;

    Content = new StackLayout {
      HorizontalOptions = LayoutOptions.Center,
      VerticalOptions = LayoutOptions.Center,
      Children = {
        new StackLayout {
          Orientation = StackOrientation.Horizontal,
          Children = {
            new Label{ Text = "Name:", FontSize = Device.GetNamedSize (NamedSize.Medium, typeof(Label)), HorizontalOptions = LayoutOptions.FillAndExpand },
            nameLabel
          }
        },
        ...
        navigateButton
      }
    };
  }

  async void OnNavigateButtonClicked (object sender, EventArgs e)
  {
    await Navigation.PopAsync ();
  }
}

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

Данные, переданные через объект BindingContext

Дополнительные сведения о привязке данных см. в статье Основы привязки данных.

Управление стеком навигации

Свойство Navigation предоставляет свойство NavigationStack, из которого могут быть получены страницы в стеке навигации. Хотя Xamarin.Forms поддерживает доступ к стеку навигации, свойство Navigation предоставляет методы InsertPageBefore и RemovePage для управления стеком путем вставки страниц или их удаления.

Метод InsertPageBefore вставляет указанную страницу в стек навигации перед указанной существующей страницей, как показано на следующей схеме.

Вставка страницы в стек навигации

Метод RemovePage удаляет указанную страницу из стека навигации, как показано на следующей схеме.

Удаление страницы из стека навигации

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

async void OnLoginButtonClicked (object sender, EventArgs e)
{
  ...
  var isValid = AreCredentialsCorrect (user);
  if (isValid) {
    App.IsUserLoggedIn = true;
    Navigation.InsertPageBefore (new MainPage (), this);
    await Navigation.PopAsync ();
  } else {
    // Login failed
  }
}

При условии что учетные данные пользователя верны, экземпляр MainPage вставляется в стек навигации перед текущей страницей. Метод PopAsync удаляет текущую страницу из стека навигации, а активной страницей становится экземпляр MainPage.

Отображение представлений на панели навигации

Любой Xamarin.FormsView объект можно отобразить на панели навигации.NavigationPage Для этого нужно установить присоединенное свойство NavigationPage.TitleView в View. Это присоединенное свойство может быть задано для любого объекта Page, и, когда Page помещается в NavigationPage, NavigationPage будет учитывать значение этого свойства.

В следующем примере показано, как задать присоединенное NavigationPage.TitleView свойство из XAML:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="NavigationPageTitleView.TitleViewPage">
    <NavigationPage.TitleView>
        <Slider HeightRequest="44" WidthRequest="300" />
    </NavigationPage.TitleView>
    ...
</ContentPage>

Вот эквивалент в коде C#:

public class TitleViewPage : ContentPage
{
    public TitleViewPage()
    {
        var titleView = new Slider { HeightRequest = 44, WidthRequest = 300 };
        NavigationPage.SetTitleView(this, titleView);
        ...
    }
}

В результате объект Slider отображается на панели навигации на NavigationPage:

TitleView ползунка

Внимание

Многие представления не будут отображаться на панели навигации, если не указан размер представления с помощью свойств WidthRequest и HeightRequest. Кроме того, представление может быть заключено в StackLayout со свойствами HorizontalOptions и VerticalOptions, для которых установлены соответствующие значения.

Поскольку класс Layout является производным от класса View, присоединенное свойство TitleView можно настроить для отображения класса макета, содержащего несколько представлений. В iOS и на универсальной платформе Windows (UWP) высоту панели навигации нельзя изменить, поэтому она будет обрезана, если представление, отображаемое на панели навигации, больше, чем размер панели навигации по умолчанию. В Android высоту панели навигации можно изменить, задав для привязываемого свойства NavigationPage.BarHeight значение double, представляющее новую высоту. Дополнительные сведения см. в разделе Установка высоты панели навигации в объекте NavigationPage.

Также панель навигации можно расширить, если поместить некоторое содержимое на панели навигации, а некоторое — в представление в верхней части страницы таким образом, чтобы цвет совпадал с панелью навигации. Кроме того, в iOS разделительную линию и тень в нижней части панели навигации можно удалить, установив для привязываемого свойства NavigationPage.HideNavigationBarSeparator значение true. Дополнительные сведения см. в разделе Сокрытие разделителя панели навигации в объекте NavigationPage.

Примечание.

Свойства BackButtonTitle, Title, TitleIcon и TitleView позволяют определять значения, которые занимают место на панели навигации. Хотя размер панели навигации зависит от платформы и размера экрана, установка всех этих свойств приведет к конфликтам из-за ограничений свободного пространства. Вместо комбинации этих свойств лучше задать желаемый дизайн панели навигации только с помощью свойства TitleView.

Ограничения

Существует ряд ограничений, которые следует учитывать при отображении объекта View на панели навигации NavigationPage.

  • В iOS представления, размещенные на панели навигации NavigationPage, отображаются по-разному в зависимости от того, включены ли крупные заголовки. Дополнительные сведения о включении крупных заголовков см. в разделе Отображение крупных заголовков.
  • В Android можно поместить представления на панели навигации NavigationPage только в приложениях, использующих совместимость приложений.
  • Не рекомендуется помещать большие и сложные представления, например ListView и TableView, в строке навигации NavigationPage.