第二十四章:页面导航(一)

不同类型的计算环境倾向于开发用于向用户呈现信息的不同隐喻。有时,在一个环境中开发的隐喻非常好,它会影响其他环境。
在万维网上发展的页面和导航比喻就是这种情况。在此之前,桌面计算机应用程序根本没有围绕可导航页面的概念进行组织。但网络展示了页面隐喻的强大功能和便利性,现在移动和桌面操作系统通常支持基于页面的架构,许多应用程序都利用了这一点。
页面体系结构在移动应用程序中特别流行,因此Xamarin.Forms支持这种体系结构。 Xamarin.Forms应用程序可以包含从ContentPage派生的多个类,用户可以在这些页面之间导航。 (在下一章中,您将看到ContentPage的几种替代方法。其他页面类型也可以参与导航。)
通常,页面将包括用户点击以导航到另一页面的按钮(或者可能是标签或具有TapGestureRecognizer的图像)。有时,第二页将允许进一​​步导航到其他页面。
但是也必须有一种方式让用户返回上一页,这里是平台差异开始显现的地方:Android和Windows Phone设备在底部包含一个标准的Back按钮(象征为左箭头或三角形)屏幕; iOS设备没有,Windows也不在桌面或平板电脑上运行。
此外,正如您将看到的,标准软件后退按钮作为iOS和Android标准用户界面的一部分提供在一些(但不是全部)可导航页面的顶部,也可以在桌面计算机上运行时通过Windows运行时提供。
从程序员的角度来看,页面导航是使用熟悉的堆栈概念实现的。当一个页面导航到另一个页面时,新页面将被压入堆栈并成为活动页面。当第二页返回到第一页时,将从堆栈中弹出一个页面,然后新的最顶层页面变为活动状态。应用程序可以访问Xamarin.Forms为应用程序维护的导航堆栈,并支持通过插入页面或删除它们来操作堆栈的方法。
围绕多个页面构建的应用程序总是有一个特殊的页面,因为它是应用程序的起点。这通常称为主页面,主页面或起始页面。
然而,应用程序中的所有其他页面与该起始页面本质上不同,因为它们分为两个不同的类别:模态页面和无模式页面。

模态页面和无模式页面

在用户界面设计中,“模态”是指在应用程序可以继续之前需要用户交互的内容。 桌面上的计算机应用程序有时会显示模态窗口或模式对话框。 当显示其中一个模态对象时,用户不能简单地使用鼠标切换到应用程序的主窗口。 模态对象在消失之前需要用户更多关注。
当需要区分这两种类型时,非模态的窗口或对话通常称为无模式。
Xamarin.Forms页面导航系统同样通过定义页面可以调用以导航到另一个页面的两种不同方法来实现模态和无模式页面:

Task PushAsync(Page page)
Task PushModalAsync(Page page)

要导航到的页面作为参数传递。 正如第二种方法的名称所暗示的那样,它导航到模态页面。 简单的PushAsync方法导航到无模式页面,在现实生活中编程是更常见的页面类型。
定义了另外两种方法返回上一页:

Task<Page> PopAsync()
Task<Page> PopModalAsync()

在许多情况下,如果应用程序依赖于手机或操作系统提供的后退导航,则无需直接调用PopAsync。
这些Push和Pop方法名称上的Task返回值和Async后缀表明它们是异步的,但这并不意味着导航页面在不同的执行线程中运行!完成任务表明的内容将在本章后面讨论。
这四种方法以及其他导航方法和属性都在INavigation界面中定义。实现此接口的对象是Xamarin.Forms的内部对象,但VisualElement定义了一个名为Navigation的类型为INavigation的只读属性,这使您可以访问导航方法和属性。
这意味着您可以从任何派生自VisualElement的类的实例中使用这些导航方法。但是,通常,您将使用页面对象的Navigation属性,因此
导航到新页面的代码通常如下所示:

await Navigation.PushAsync(new MyNewPage());

或这个:

await Navigation.PushModalAsync(new MyNewModalPage());

模态页面和无模式页面之间的区别主要涉及操作系统在页面上提供的用户界面以返回到上一页面。这种差异因平台而异。 iOS和Windows桌面或平板电脑上存在无模式和模态页面之间用户界面的更大差异;在Android和Windows手机平台上发现差异较小。
通常,当您的应用程序需要来自用户的某些信息并且您不希望用户返回上一页时,您将使用模态页面,直到提供该信息为止。要在所有平台上工作,模态页面必须提供自己的用户界面,以便导航回上一页。
让我们首先更详细地探索无模式页面和模态页面之间的区别。 ModelessAndModal程序包含三个页面,其类名为MainPage,ModalPage和ModelessPage。页面本身相当简单,因此为了将文件批量保持在最低限度,这些是仅代码页面。在实际应用程序中,页面可以使用XAML实现,也可以通过代码动态生成。 (您将在本章后面看到这两个选项的示例。)
MainPage创建两个Button元素,一个导航到无模式页面,另一个导航到模态页面。请注意构造函数顶部的Title属性设置。此Title属性在单页面应用程序中无效,但在多页面应用程序中起着重要作用:

public class MainPage : ContentPage
{
    public MainPage()
     {
        Title = "Main Page";
        Button gotoModelessButton = new Button
        {
            Text = "Go to Modeless Page",
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.CenterAndExpand
        };
        gotoModelessButton.Clicked += async (sender, args) =>
        {
            await Navigation.PushAsync(new ModelessPage());
        };
        Button gotoModalButton = new Button
        {
            Text = "Go to Modal Page",
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.CenterAndExpand
        };
        gotoModalButton.Clicked += async (sender, args) =>
        {
            await Navigation.PushModalAsync(new ModalPage());
        };
        Content = new StackLayout
        {
            Children =
            {
                gotoModelessButton,
                gotoModalButton
            }
        };
    }
}

第一个Button的Clicked处理程序使用ModelessPage的新实例调用PushAsync,第二个调用PushModalAsync和一个新的ModalPage实例。 Clicked处理程序使用async关键字进行标记,并使用await调用Push方法。
调用PushAsync或PushModalAsync的程序必须在App类的构造函数中具有稍微不同的启动代码。 不是将App的MainPage属性设置为应用程序唯一页面的实例,而是将应用程序的启动页面的实例传递给NavigationPage构造函数,并将其设置为MainPage属性。
以下是应用程序包含页面导航时App类的构造函数的外观:

public class App : Application
{
    public App()
    {
        MainPage = new NavigationPage(new MainPage());
    }
    __
}

本章所有程序中的大多数App类都包含类似的代码。 作为替代方案,您可以使用其无参数构造函数实例化NavigationPage,然后调用NavigationPage的PushAsync方法转到主页。
使用NavigationPage会在页面中产生明显的差异。 Title属性显示在MainPage的顶部,并伴随着Android屏幕上的应用程序图标:
第二十四章:页面导航(一)
另一个很大的区别是你不再需要在iOS页面上设置Padding以避免覆盖屏幕顶部的状态栏。
标题也显示在以平板电脑模式运行的Windows 10程序的顶部:
第二十四章:页面导航(一)
Windows 8.1和Windows Phone 8.1平台上会显示一个相当大的标题:
第二十四章:页面导航(一)
单击“转到无模式页面”按钮将导致执行以下代码:

await Navigation.PushAsync(new ModelessPage());

此代码实例化一个新的ModelessPage并导航到该页面。
ModelessPage类定义了一个Title属性,其中包含文本“Modeless Page”和一个标记为Back to Main的Button元素,其中一个Clicked处理程序调用PopAsync:

public class ModelessPage : ContentPage
{
    public ModelessPage()
    {
        Title = "Modeless Page";
        Button goBackButton = new Button
        {
            Text = "Back to Main",
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.Center
        };
        goBackButton.Clicked += async (sender, args) =>
        {
            await Navigation.PopAsync();
        };
        Content = goBackButton;
    }
}

您实际上并不需要iOS和Android页面上的Back to Main按钮,因为页面顶部的左箭头表示执行相同的功能。 Windows Phone不需要那个Button,因为它在屏幕底部有一个Back按钮,Android设备也是如此:
第二十四章:页面导航(一)
iOS和Android页面上的顶部区域称为导航栏。 在该导航栏中,iOS和Android页面都显示当前页面的Title属性,iOS页面还以另一种颜色显示上一页面的Title属性。
在Windows 10下以平板电脑模式运行的程序在左下角包含一个“后退”按钮,直接位于Windows徽标的右侧:
第二十四章:页面导航(一)
相反,Windows 8.1程序以带圆圈的箭头形式显示一个按钮,以导航回上一页。 Windows Phone 8.1屏幕不需要该按钮,因为它在屏幕底部有一个“后退”按钮:
第二十四章:页面导航(一)
总之,您无需在无模式页面上包含您自己的返回主按钮(或其等效按钮)。 导航界面或设备本身都提供“后退”按钮。
我们回到MainPage。 单击主页面上的“转到模态页面”按钮时,“单击”处理程序将执行以下代码:

await Navigation.PushModalAsync(new ModalPage(), true);

除了不同的Title设置和Clicked处理程序中对PopModalAsync的调用之外,ModalPage类几乎与ModelessPage相同:

public class ModalPage : ContentPage
{
    public ModalPage()
    {
        Title = "Modal Page";
        Button goBackButton = new Button
        {
            Text = "Back to Main",
            HorizontalOptions = LayoutOptions.Center,
            VerticalOptions = LayoutOptions.Center
        };
        goBackButton.Clicked += async (sender, args) =>
        {
            await Navigation.PopModalAsync();
        };
        Content = goBackButton;
    }
}

尽管类中的Title属性设置,但三个平台中没有一个显示Title或任何其他页面导航界面:
第二十四章:页面导航(一)
这些屏幕现在看起来像典型的单页面应用程序。 虽然不是很明显,但您还需要小心避免覆盖iOS页面上的状态栏。
你不需要Android和Windows Phone页面上的Back to Main按钮,因为你可以使用手机底部的Back按钮,但你肯定需要在iOS页面上:iPhone上的Back to Main按钮是 返回MainPage的唯一路径。
在平板电脑模式下在Windows 10下运行的UWP应用程序不显示标题,但左下角的“后退”按钮仍可用于导航回MainPage。
第二十四章:页面导航(一)
但是,Windows 8.1页面需要“返回主”按钮,而Windows Phone 8.1页面则不需要,因为它在手机底部有一个“后退”按钮:
第二十四章:页面导航(一)
页面定义内部没有任何内容可以区分无模式页面和模态页面。这取决于页面的调用方式 - 无论是通过PushAsync还是PushModalAsync。但是,特定页面必须知道如何调用它,以便它可以调用PopAsync或PopModalAsync来导航回来。
在该程序运行的整个过程中,只有一个MainPage实例。当ModelessPage和ModalPage处于活动状态时,它仍然存在。在多页面应用程序中始终如此。当下一页处于活动状态时,调用PushAsync或PushModalAsync的页面不会停止存在。
但是,在此程序中,每次导航到ModelessPage或ModalPage时,都会创建该页面的新实例。当该页面返回到MainPage时,没有对该ModelessPage或ModalPage实例的进一步引用,并且该对象符合垃圾回收的条件。这不是管理可导航页面的唯一方法,您将在本章后面看到替代方案,但通常最好在导航到页面之前对其进行实例化。
页面始终占据整个屏幕。有时,模态页面只占据屏幕的一部分,并且前一页面在弹出窗口下可见(但禁用)。您无法使用Xamarin.Forms页面执行此操作。如果你想要这样的东西,请看第14章“绝对布局”中的SimpleOverlay程序。