分层导航
NavigationPage 类提供分层导航体验,用户可以随心所欲地向前或向后导航页面。 此类将导航实现为页对象的后进先出 (LIFO) 堆栈。 本文演示如何使用 NavigationPage 类在页面的堆栈中执行导航。
若要从一页移动到另一页,应用程序会将新页推送到导航堆栈中,在堆栈中,该页会变为活动页,如以下关系图中所示:
若要返回到前一页,应用程序会从导航堆栈弹出当前页面,而使最顶层的新页面成为活动页面,如以下关系图中所示:
可以由任何 Page
派生类型上的 Navigation
属性公开导航方法。 这些方法能够将页面推送到导航堆栈、从导航堆栈中弹出页面以及执行堆栈操作。
执行导航
在分层导航中,使用 NavigationPage
类在 ContentPage
对象的堆栈内进行导航。 以下屏幕截图显示了每个平台上 NavigationPage
的主要组件:
NavigationPage
的布局取决于平台:
- 在 iOS 中,页面顶部有一个显示标题的导航栏,其上有一个“Back”按钮,可以返回前一页。
- 在 Android 中,页面顶部有一个显示标题、图标的导航栏,其上有一个“Back”按钮,可以返回前一页。 在
[Activity]
属性中定义图标,该属性修饰特定于 Android 平台的项目中的MainActivity
类。 - 在通用 Windows 平台上,显示标题的页面顶部有一个导航栏。
在所有平台上,Page.Title
属性的值将显示为页面标题。 此外,IconColor
属性可以设置为应用于导航栏中的图标的 Color
。
注意
建议只使用 ContentPage
实例填充 NavigationPage
。
创建根页
添加到导航堆栈中的第一页称为应用程序的根页,以下代码示例显示了实现此过程的方法:
public App ()
{
MainPage = new NavigationPage (new Page1Xaml ());
}
这会将 Page1Xaml
ContentPage
实例推送到导航堆栈中,在堆栈中,它成为应用程序的活动页和根页。 以下屏幕截图演示了此过程:
注意
NavigationPage
实例的 RootPage
属性可提供对导航堆栈中的第一页的访问权限。
将页面推送到导航堆栈
若要导航到 Page2Xaml
,需要对当前页的 Navigation
属性调用 PushAsync
方法,如以下代码示例所示:
async void OnNextPageButtonClicked (object sender, EventArgs e)
{
await Navigation.PushAsync (new Page2Xaml ());
}
这会将 Page2Xaml
实例推送到导航堆栈中,在堆栈中,它成为活动页。 以下屏幕截图演示了此过程:
调用 PushAsync
方法后,会发生以下事件:
- 调用
PushAsync
的页面会调用其OnDisappearing
替代。 - 要导航到的页面会调用其
OnAppearing
替代。 PushAsync
任务完成。
但是,这些事件发生的确切顺序取决于平台。 有关详细信息,请参阅 Charles Petzold 所著的 Xamarin.Forms 书籍的第 24 章。
注意
不能将对 OnDisappearing
和 OnAppearing
替代的调用视为绝对的页面导航指示。 例如,在 iOS 上,应用程序终止后,将对活动页面调用 OnDisappearing
替代。
从导航堆栈中弹出页面
通过设备上的返回按钮(无论是设备上的物理按钮还是屏幕按钮),可以从导航堆栈中弹出活动页。
若要以编程方式返回原始页,Page2Xaml
实例必须调用 PopAsync
方法,如以下代码示例所示:
async void OnPreviousPageButtonClicked (object sender, EventArgs e)
{
await Navigation.PopAsync ();
}
这会从导航堆栈中删除 Page2Xaml
实例,而使最顶层的页成为活动页。 调用 PopAsync
方法后,会发生以下事件:
- 调用
PopAsync
的页面会调用其OnDisappearing
替代。 - 要返回到的页面会调用其
OnAppearing
替代。 PopAsync
任务返回。
但是,这些事件发生的确切顺序取决于平台。 有关详细信息,请参阅 Charles Petzold 所著的 Xamarin.Forms 书籍的第 24 章。
除了 PushAsync
和 PopAsync
方法之外,每个页面的 Navigation
属性还提供了 PopToRootAsync
方法,如下面的代码示例所示:
async void OnRootPageButtonClicked (object sender, EventArgs e)
{
await Navigation.PopToRootAsync ();
}
该方法将弹出导航堆栈中除根 Page
之外的所有内容,从而使应用程序的根页面成为活动页面。
对页面过渡效果进行动画处理
每个页面的 Navigation
属性还提供已替代的 push 和 pop 方法,这些方法包含一个 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
则会启用页面过渡动画,前提是基础平台支持该动画。 但是,缺少此参数的 push 和 pop 方法默认启用该动画。
导航时传递数据
有时,页面必须在导航期间将数据传递到另一个页面。 实现此操作的两种方法是:通过页面构造函数传递数据,将新页面的 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);
}
此代码将 SecondPage
实例的 BindingContext
设置为 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
控件显示在页面上,如以下屏幕截图所示:
若要深入了解数据绑定,请参阅数据绑定基本知识。
操作导航堆栈
可使用 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
会遵守属性的值。
以下示例演示如何从 XAML 设置 NavigationPage.TitleView
附加属性:
<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
的导航栏中:
重要
很多视图不会出现在导航栏中,除非使用 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
属性来更好地实现所需的导航栏设计。
限制
当在 NavigationPage
的导航栏中显示 View
时,需要注意一些限制:
- 在 iOS 中,放置在
NavigationPage
导航栏中的视图会根据是否启用大标题显示在不同的位置。 有关启用大标题的详细信息,请参阅显示大标题。 - 在 Android 上,只有在使用 app-compat 的应用程序中,才能在
NavigationPage
的导航栏中放置视图。 - 不建议在
NavigationPage
的导航条中放置大而复杂的视图,如ListView
和TableView
。