Xamarin.Forms 导航栏 Navigation (三) 电脑版发表于:2022/4/7 17:10  >#Xamarin.Forms 导航栏 Navigation (三) [TOC] ##添加导航栏 tn2>一个常规的App是由多个Page组成的,出现多个Page就会涉及页面跳转问题。Xamarin.Forms页面之间的跳转通过Navigation Stack管理Page,如页面A跳转到页面B时,会将B压入栈定,此时页面B成为活动页面,执行Back操作时,页面B从栈定推出使页面A重新变为活动页面。每个应用程序都有一个特殊页作为应用程序的入口(main page, or the home page, or the start page),Xamarin.Forms中由App的MainPage属性设置。 ```csharp public partial class App : Application { public App() { InitializeComponent(); MainPage = new NavigationPage(new MainPage()); } } ```  ## 添加新页面 tn2>我们希望通过上一篇文章所选的集合子选项,弹出一个新的页面。 首先我们先添加一个新的页面`DetailPage.xaml`。 创建一个显示我选中内容的`Label`,并且通过设置`VerticalOptions`属性值为`CenterAndExpand`来进行垂直居中,然后通过设置`HorizontalOptions`属性来进行水平居中;还创建一个`Dismiss`按钮用于关闭当前的页面,在这里呢我让`Label`的`Text`属性绑定了提供数据的`DetailPageViewModel.NoteText`属性,`Dismiss`按钮的点击事件绑定了`DetailPageViewModel.DismissPageCommand`命令。 ```xml <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="FirstApp.DetailPage"> <ContentPage.Content> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height=".2*" /> </Grid.RowDefinitions> <Label Text="{Binding NoteText}" FontSize="Title" Grid.Row="0" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" /> <Button Grid.Row="1" Text="Dismiss" Command="{Binding DismissPageCommand}" /> </Grid> </ContentPage.Content> </ContentPage> ``` tn2>接着我们来查看`DetailPageViewModel`数据源的实体定义。`DetailPageViewModel`同样实现了`INotifyPropertyChanged`接口,并创建了一个`NoteText`属性用于显示所选择的内容值,`DismissPageCommand`通过`await Application.Current.MainPage.Navigation.PopModalAsync();`用于当前用户界面退出页面栈达到一个退出当前页面的功能。 ```csharp public class DetailPageViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public DetailPageViewModel(string note) { DismissPageCommand = new Command(async () => { await Application.Current.MainPage.Navigation.PopModalAsync(); }); NoteText = note; } string noteText; public string NoteText { get => noteText; set { noteText = value; var args = new PropertyChangedEventArgs(nameof(NoteText)); PropertyChanged?.Invoke(this, args); } } public Command DismissPageCommand { get; } } ``` tn2>在我们首页所绑定的数据源`MainPageViewModel`类中,当前集合中所选择文本定义`SelectedNote`属性。以及定义用户界面所选择集合子选项所触发的`SelectedNoteChangedCommand`命令。在触发命令时,我们将创建一个新的页面`DetailPage`,并提供我们所选的子选项内容来创建数据源`DetailPageViewModel`,通过`detailPage.BindingContext`来绑定`DetailPage`所需要的数据。最后通过`await Application.Current.MainPage.Navigation.PushModalAsync`来将这个新的页面压入页面栈顶呈现给用户。 ```csharp public class MainPageViewModel : INotifyPropertyChanged { public Command SelectedNoteChangedCommand { get; } string selectedNote; public string SelectedNote { get => selectedNote; set { selectedNote = value; var args = new PropertyChangedEventArgs(nameof(SelectedNote)); PropertyChanged?.Invoke(this, args); } } public MainPageViewModel() { SelectedNoteChangedCommand = new Command(async ()=> { //创建一个新的页面 var detailVM = new DetailPageViewModel(SelectedNote); var detailPage = new DetailPage(); detailPage.BindingContext = detailVM; //向应用端推送页面 await Application.Current.MainPage.Navigation.PushModalAsync(detailPage); }); EraseCommand = new Command(() => { TheNote = string.Empty; }); SaveCommand = new Command(() => { AllNotes.Add(TheNote); TheNote = string.Empty; }); } public ObservableCollection<string> AllNotes { get; set; } = new ObservableCollection<string>(); public event PropertyChangedEventHandler PropertyChanged; string theNote; public string TheNote { get => theNote; set { theNote = value; var args = new PropertyChangedEventArgs(nameof(TheNote)); PropertyChanged?.Invoke(this, args); } } public Command SaveCommand { get; } public Command EraseCommand { get; } } ``` tn2>接着我们需要在前端绑定好我们相关的定义。 ```xml <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:FirstApp" x:Class="FirstApp.MainPage"> <ContentPage.BindingContext> <local:MainPageViewModel/> </ContentPage.BindingContext> <Grid> <Grid.RowDefinitions> <RowDefinition Height="*"/> <RowDefinition Height="2*"/> <RowDefinition Height=".5*"/> <RowDefinition Height="2*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Image Source="logo_xamarin" BackgroundColor="PowderBlue" Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" /> <Editor Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1" Placeholder="Enter Note Here" Text="{Binding TheNote}" /> <Button Grid.Row="2" Grid.Column="0" Text="Save" Command="{Binding SaveCommand}" /> <Button Grid.Row="2" Grid.Column="1" Text="Erase" Command="{Binding EraseCommand}" /> <CollectionView ItemsSource="{Binding AllNotes}" SelectionMode="Single" SelectedItem="{Binding SelectedNote}" SelectionChangedCommand="{Binding SelectedNoteChangedCommand}" Grid.Row="3" Grid.ColumnSpan="2" Grid.Column="0"> <CollectionView.ItemTemplate> <DataTemplate> <StackLayout Padding="10,10"> <Frame> <Label Text="{Binding .}" FontSize="Title"/> </Frame> </StackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </Grid> </ContentPage> ```    ## Xamarin.Forms Navigation tn2>Xamarin.Forms 提供许多不同的页面导航体验,具体取决于所使用的页面类型。  tn2>目前我们使用的是`NavigationPage`分层导航类型。 >### 分层导航 tn2>该类NavigationPage提供分层导航体验,用户可以根据需要在页面中向前和向后导航。该类将导航实现为Page对象的后进先出 (LIFO) 堆栈。通过Push可以推送到页面栈顶端,通过Pop方法可以退出页面栈顶端。  >### 创建根页面 tn2>添加到导航堆栈的第一个页面称为应用程序的根页面,我们在添加导航栏时讲到了,以下代码示例显示了这是如何完成的: ```csharp public App () { MainPage = new NavigationPage(new MainPage()); } ``` tn2>这会导致`MainPage`ContentPage实例被推送到导航堆栈上,在那里它成为应用程序的活动页面和根页面。 >### 将页面推送到导航堆栈 tn2>要导航到`Page2Xaml`,需要在当前页面PushAsync的属性上调用方法Navigation,如以下代码示例所示: ```csharp await Navigation.PushAsync(new Page2Xaml()); # 全局调用 await Application.Current.MainPage.Navigation.PushAsync(new Page2Xaml()); ``` tn2>调用该PushAsync方法时,会发生以下事件: -- 页面调用调用PushAsync了它的`OnDisappearing`事件。 -- 被导航到的页面`OnAppearing`调用了它的事件。 | 事件名 | 描述 | | ------------ | ------------ | | `OnDisappearing` | 当前页面被覆盖时,页面所触发的事件。 | | `OnAppearing` | 当前页面被覆盖时,显示下一个页面之前所触发的事件。 | ```csharp public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); } protected override void OnAppearing() { base.OnAppearing(); } protected override void OnDisappearing() { base.OnDisappearing(); } } ``` >### 从导航堆栈中退出页面 tn2>活动页面可以通过按下设备上的后退按钮从导航堆栈中弹出,无论这是设备上的物理按钮还是屏幕按钮。 要以编程方式返回原始页面,`Page2Xaml`实例必须调用该`PopAsync`方法,如以下代码示例所示: ```csharp await Navigation.PopAsync(); # 全局调用 await Application.Current.MainPage.Navigation.PopAsync(); ``` tn2>这会从导航堆栈中删除 Page2Xaml 实例,而使最顶层的页成为活动页。 调用 PopAsync 方法后,会发生以下事件: 调用 PopAsync 的页面会调用其 `OnDisappearing` 事件。 要返回到的页面会调用其 `OnAppearing` 事件。 PopAsync 任务返回。 tn>当然它们都还有一个构造`PopAsync(Boolean)`与`PushAsync(Boolean)`,这个可以以**动画**的方式进行删除。 >### 关于不同的导航进行退出与呈现的区别 | 方法 | 描述 | | ------------ | ------------ | | `PushAsync` | 显示某个页面 | | `PopAsync` | 退出当前页面 | | `PushModalAsync` | 以模态的方式显示某个页面(相当于以弹出的页面为主) | | `PopModalAsync` | 退出当前模态页面 | | `PopToRootAsync` | 退出主页 | | `RemovePage` | 删除某个页面 | >### 在导航栏中显示滚动条 tn2>任何 `Xamarin.Forms View` 中可以显示在导航栏 NavigationPage 中。 这是通过将 `NavigationPage.TitleView` 附加属性设置为 `View` 来实现的。 此附加属性可以在任何 `Page` 上设置,当 `Page` 被推送到 `NavigationPage` 上后,`NavigationPage` 会遵守属性的值。 ```csharp <?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:FirstApp" x:Class="FirstApp.MainPage"> <NavigationPage.TitleView> <Slider HeightRequest="44" WidthRequest="300" /> </NavigationPage.TitleView> .... </ContentPage> ``` tn2>下面是等效 C# 代码: ```csharp public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); var titleView = new Slider { HeightRequest = 44, WidthRequest = 300 }; NavigationPage.SetTitleView(this, titleView); } protected override void OnAppearing() { base.OnAppearing(); } protected override void OnDisappearing() { base.OnDisappearing(); } } ``` 