Xamarin.Forms Shell 导航
Xamarin.Forms Shell 包括基于 URI 的导航体验:使用路由导航到应用程序中的任何页面,而无需遵循设置的导航层次结构。 此外,它还能够向后导航,不必访问导航堆栈上的所有页面。
Shell
类定义以下与导航相关的属性:
BackButtonBehavior
属于BackButtonBehavior
类型,是用于定义“后退”按钮行为的附加属性。CurrentItem
属于ShellItem
类型,是当前选定的项。CurrentPage
属于Page
类型,是当前显示的页面。CurrentState
属于ShellNavigationState
类型,是当前Shell
的导航状态。Current
属于Shell
类型,是Application.Current.MainPage
的类型转换别名。
BackButtonBehavior
、CurrentItem
和 CurrentState
属性由 BindableProperty
对象提供支持,这意味着这些属性可以作为数据绑定的目标。
导航是通过从 Shell
类调用 GoToAsync
方法来执行的。 即将执行导航时,将触发 Navigating
事件,并在导航完成时触发 Navigated
事件。
注意
仍可在 Shell 应用程序中的页面间通过使用 Navigation 属性执行导航。 有关详细信息,请参阅分层导航。
Routes
通过指定要导航到的 URI,可以在 Shell 应用程序中执行导航。 导航 URI 可以有三个组件:
- 一个路由,它定义了作为 Shell 视觉层次结构的一部分存在的内容的路径。
- 一个页。 Shell 视觉层次结构中不存在的页可以从 Shell 应用程序中的任何位置推送到导航堆栈。 例如,不会在 Shell 视觉层次结构中定义详细信息页,但可以根据需要将其推送到导航堆栈。
- 一个或多个查询参数。 查询参数是可以在导航时传递到目标页的参数。
当导航 URI 包含所有三个组件时,结构为://route/page?queryParameters
注册路由
可以通过 FlyoutItem
、TabBar
、Tab
和 ShellContent
对象的 Route
属性在这些对象上定义路由:
<Shell ...>
<FlyoutItem ...
Route="animals">
<Tab ...
Route="domestic">
<ShellContent ...
Route="cats" />
<ShellContent ...
Route="dogs" />
</Tab>
<ShellContent ...
Route="monkeys" />
<ShellContent ...
Route="elephants" />
<ShellContent ...
Route="bears" />
</FlyoutItem>
<ShellContent ...
Route="about" />
...
</Shell>
注意
Shell 层次结构中的所有项都有一个与之关联的路由。 如果未设置路由,则会在运行时生成一个路由。 但是,不能保证生成的路由在不同应用程序会话之间都是一致的。
上述示例创建了以下路由层次结构,可用于编程导航:
animals
domestic
cats
dogs
monkeys
elephants
bears
about
要导航到 dogs
路由的 ShellContent
对象,绝对路由 URI 为 //animals/domestic/dogs
。 同样,要导航到 about
路由的 ShellContent
对象,绝对路由 URI 为 //about
。
警告
如果检测到重复路由,则将在应用程序启动时引发 ArgumentException
。 如果层次结构中同一级别的两个或更多路由共享一个路由名称,也会引发此异常。
注册详细信息页路由
在 Shell
子类构造函数或者在调用路由前运行的任何其他位置中,可为未在 Shell 视觉层次结构中表示的任何详细信息页面显式地注册其他路由。 这是使用 Routing.RegisterRoute
方法完成的:
Routing.RegisterRoute("monkeydetails", typeof(MonkeyDetailPage));
Routing.RegisterRoute("beardetails", typeof(BearDetailPage));
Routing.RegisterRoute("catdetails", typeof(CatDetailPage));
Routing.RegisterRoute("dogdetails", typeof(DogDetailPage));
Routing.RegisterRoute("elephantdetails", typeof(ElephantDetailPage));
此示例将 Shell
子类中未定义的详细信息页注册为路由。 然后可以使用基于 URI 的导航从应用程序中的任何位置导航到这些详细信息页面。 这些页面的路由被称为“全局路由”。
警告
如果 Routing.RegisterRoute
方法尝试将同一路由注册到两个或多个不同类型,将引发 ArgumentException
。
此外,如果需要,页面可在不同的路由层次结构上注册:
Routing.RegisterRoute("monkeys/details", typeof(MonkeyDetailPage));
Routing.RegisterRoute("bears/details", typeof(BearDetailPage));
Routing.RegisterRoute("cats/details", typeof(CatDetailPage));
Routing.RegisterRoute("dogs/details", typeof(DogDetailPage));
Routing.RegisterRoute("elephants/details", typeof(ElephantDetailPage));
此示例启用上下文页面导航,其中从 monkeys
路由的页面导航到 details
路由将显示 MonkeyDetailPage
。 同样,从 elephants
路由的页面导航到 details
路由将显示 ElephantDetailPage
。 有关详细信息,请参阅上下文导航。
注意
如果需要,已经使用 Routing.RegisterRoute
方法注册其路由的页面可以通过 Routing.UnRegisterRoute
方法注销。
执行导航
要执行导航,必须首先获得对 Shell
子类的引用。 通过将 App.Current.MainPage
属性转换为 Shell
对象,或者通过 Shell.Current
属性,可以获得此引用。 然后,可以通过调用 Shell
对象上的 GoToAsync
方法来执行导航。 该方法导航到 ShellNavigationState
并返回 Task
,后者将在导航动画完成后完成。 ShellNavigationState
对象是通过 GoToAsync
方法从 string
或 Uri
构造的,并将其 Location
属性设置为 string
或 Uri
参数。
重要说明
当导航到 Shell 视觉层次结构中的路由时,不会创建导航堆栈。 但是,当导航到不在 Shell 视觉层次结构中的页面时,将创建一个导航堆栈。
可以通过 Shell.Current.CurrentState
属性检索 Shell
对象的当前导航状态,该属性包括 Location
属性中显示的路由的 URI。
绝对路由
可以通过将一个有效的绝对 URI 指定为 GoToAsync
方法的参数来执行导航:
await Shell.Current.GoToAsync("//animals/monkeys");
本示例导航到 monkeys
路由的页面,该路由在 ShellContent
对象上定义。 表示 monkeys
路由的 ShellContent
对象是其路由为 animals
的 FlyoutItem
对象的子对象。
相对路由
还可以通过将一个有效的相对 URI 指定为 GoToAsync
方法的参数来执行导航。 路由系统将尝试将 URI 与 ShellContent
对象进行匹配。 因此,如果应用程序中的所有路由都是唯一的,那么仅可通过将唯一路由名称指定为相对 URI 来执行导航。
支持下列相对路由格式:
格式 | 描述 |
---|---|
路由 | 将从当前位置向上搜索路由层次结构来获取指定的路由。 匹配的页面将被推送到导航堆栈。 |
/路由 | 将在指定路由中从当前位置向下搜索路由层次结构。 匹配的页面将被推送到导航堆栈。 |
//路由 | 将从当前位置向上搜索路由层次结构来获取指定的路由。 匹配的页面将替换导航堆栈。 |
///路由 | 将从当前位置向下搜索路由层次结构来获取指定的路由。 匹配的页面将替换导航堆栈。 |
以下示例导航到 monkeydetails
路由的页面:
await Shell.Current.GoToAsync("monkeydetails");
在本例中,在 monkeyDetails
路由中向上搜索层次结构,直到找到匹配的页面。 找到该页面后,会将它推送到导航堆栈。
上下文导航
相对路由支持上下文导航。 以下列路由层次结构为例:
monkeys
details
bears
details
当显示 monkeys
路由的注册页时,导航到 details
路由将显示 monkeys/details
路由的注册页。 同样,当显示 bears
路由的注册页时,导航到 details
路由将显示 bears/details
路由的注册页。 有关如何注册本示例中的路由的信息,请参阅注册页面路由。
向后导航
向后导航可以通过将“..”指定为 GoToAsync
方法的参数来执行:
await Shell.Current.GoToAsync("..");
通过“..”执行的向后导航还可与路由结合使用:
await Shell.Current.GoToAsync("../route");
在本例中,会执行向后导航,然后导航到指定的路由。
重要说明
仅当向后导航将你置于路由层次结构中的当前位置以导航到指定路由时,才可在向后导航后导航到指定路由。
同样,可以向后导航多次,然后导航到指定路由:
await Shell.Current.GoToAsync("../../route");
在本例中,会执行向后导航两次,然后导航到指定的路由。
此外,在向后导航时,可通过查询属性传递数据:
await Shell.Current.GoToAsync($"..?parameterToPassBack={parameterValueToPassBack}");
在本例中,会执行向后导航,并将查询参数值传递到上一页上的查询参数。
注意
可将查询参数追加到任何向后导航请求。
若要详细了解如何在导航时传递数据,请参阅传递数据。
无效路由
以下路由格式无效:
Format | 说明 |
---|---|
//page 或 ///page | 全局路由当前不能是导航堆栈上的唯一页面。 因此,不支持绝对路由到全局路由。 |
使用这些路由格式会导致引发 Exception
。
警告
尝试导航到不存在的路由会导致引发 ArgumentException
异常。
调试导航
一些 Shell 类通过 DebuggerDisplayAttribute
修饰,它指定调试程序如何显示类或字段。 这可以通过显示与导航请求相关的数据来帮助调试导航请求。 例如,下面的屏幕截图显示了 Shell.Current
对象的 CurrentItem
和 CurrentState
属性:
在本例中,类型为 FlyoutItem
的 CurrentItem
属性显示了 FlyoutItem
对象的标题和路由。 同样,类型为 ShellNavigationState
的 CurrentState
属性显示了 Shell 应用程序中显示的路由的 URI。
导航堆栈
Tab
类定义了一个类型为 IReadOnlyList<Page>
的 Stack
属性,它表示 Tab
中的当前导航堆栈。 该类还提供了以下可重写的导航方法:
GetNavigationStack
,返回IReadOnlyList<Page>
,表示当前导航堆栈。OnInsertPageBefore
,调用INavigation.InsertPageBefore
时会对其进行调用。OnPopAsync
,返回Task<Page>
,调用INavigation.PopAsync
时会对其进行调用。OnPopToRootAsync
,返回Task
,调用INavigation.OnPopToRootAsync
时会对其进行调用。OnPushAsync
,返回Task
,调用INavigation.PushAsync
时会对其进行调用。OnRemovePage
,调用INavigation.RemovePage
时会对其进行调用。
下面的示例演示如何重写 OnRemovePage
方法:
public class MyTab : Tab
{
protected override void OnRemovePage(Page page)
{
base.OnRemovePage(page);
// Custom logic
}
}
在此示例中,应在 Shell 视觉对象层次结构中使用 MyTab
对象,而不是使用 Tab
对象。
导航事件
Shell
类定义 Navigating
事件,该事件在即将执行导航时触发,原因可能是编程导航或用户交互。 Navigating
事件随附的 ShellNavigatingEventArgs
对象提供以下属性:
属性 | 类型 | 描述 |
---|---|---|
Current |
ShellNavigationState |
当前页的 URI。 |
Source |
ShellNavigationSource |
发生的导航类型。 |
Target |
ShellNavigationState |
表示导航目标位置的 URI。 |
CanCancel |
bool |
指示是否可以取消导航的值。 |
Cancelled |
bool |
指示是否已取消导航的值。 |
此外,ShellNavigatingEventArgs
类还提供 Cancel
方法和 GetDeferral
方法,前者可用于取消导航,后者返回可用于完成导航的 ShellNavigatingDeferral
令牌。 有关导航延迟的详细信息,请参阅导航延迟。
Shell
类还定义 Navigated
事件,该事件在导航完成时触发。 Navigated
事件随附的 ShellNavigatedEventArgs
对象提供以下属性:
属性 | 类型 | 描述 |
---|---|---|
Current |
ShellNavigationState |
当前页的 URI。 |
Previous |
ShellNavigationState |
上一页的 URI。 |
Source |
ShellNavigationSource |
发生的导航类型。 |
重要说明
触发 Navigating
事件时将调用 OnNavigating
方法。 同样,触发 Navigated
事件时将调用 OnNavigated
方法。 这两种方法都可以在 Shell
子类中被替代,以截获导航请求。
ShellNavigatedEventArgs
和 ShellNavigatingEventArgs
类均具有类型为 ShellNavigationSource
的 Source
属性。 此枚举提供下列值:
Unknown
Push
Pop
PopToRoot
Insert
Remove
ShellItemChanged
ShellSectionChanged
ShellContentChanged
因此,可以在 OnNavigating
替代中截获导航,并可根据导航源执行操作。 例如,下面的代码显示页面数据未保存时如何取消向后导航:
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
// Cancel any back navigation.
if (args.Source == ShellNavigationSource.Pop)
{
args.Cancel();
}
// }
导航延迟
可以根据用户的选择截获并完成或取消 Shell 导航。 此操作可通过以下方式实现:重写 Shell
子类中的 OnNavigating
方法,并对 ShellNavigatingEventArgs
对象调用 GetDeferral
方法。 此方法返回 ShellNavigatingDeferral
令牌,该令牌有一个可用于完成导航请求的 Complete
方法:
public MyShell : Shell
{
// ...
protected override async void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
ShellNavigatingDeferral token = args.GetDeferral();
var result = await DisplayActionSheet("Navigate?", "Cancel", "Yes", "No");
if (result != "Yes")
{
args.Cancel();
}
token.Complete();
}
}
在此示例中,将显示一个操作工作表,邀请用户完成导航请求,或取消导航。 可通过对 ShellNavigatingEventArgs
对象调用 Cancel
方法来取消导航。 通过对在 ShellNavigatingEventArgs
对象上使用 GetDeferral
方法检索到的 ShellNavigatingDeferral
令牌调用 Complete
方法来完成导航。
警告
如果用户在存在挂起的导航延迟的情况下尝试导航,GoToAsync
方法将引发 InvalidOperationException
。
传递数据
执行基于 URI 的编程导航时,可以将数据作为查询参数传递。 这通过以下方式实现:在路由后追加 ?
,后跟查询参数 ID =
和一个值。 例如,当用户在 ElephantsPage
上选择大象时,将在示例应用程序中执行以下代码:
async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}");
}
此代码示例在 CollectionView
中检索当前选中的大象,并导航到 elephantdetails
路由,将 elephantName
作为查询参数传递。
可以通过两种方法接收导航数据:
- 对于每个查询参数,可以使用
QueryPropertyAttribute
修饰表示要导航到的页面的类或页面的BindingContext
的类。 有关更多信息,请参阅使用查询属性的特性处理导航数据。 - 表示要导航到的页面的类,或页面的类
BindingContext
可以实现IQueryAttributable
接口。 有关更多信息,请参阅使用单一方法处理导航数据。
使用查询属性特性处理导航数据
可以通过使用 QueryPropertyAttribute
针对每个查询参数修饰接收类来接收导航数据:
[QueryProperty(nameof(Name), "name")]
public partial class ElephantDetailPage : ContentPage
{
public string Name
{
set
{
LoadAnimal(value);
}
}
...
void LoadAnimal(string name)
{
try
{
Animal animal = ElephantData.Elephants.FirstOrDefault(a => a.Name == name);
BindingContext = animal;
}
catch (Exception)
{
Console.WriteLine("Failed to load animal.");
}
}
}
QueryPropertyAttribute
的第一个参数指定将接收数据的属性的名称,第二个参数指定查询参数 ID。因此,上述示例中的 QueryPropertyAttribute
指定 Name
属性将接收从 GoToAsync
方法调用中的 URI 传入 name
查询参数的数据。 Name
属性资源库调用 LoadAnimal
方法来检索 name
的 Animal
对象,并将其设置为页面的 BindingContext
。
注意
通过 QueryPropertyAttribute
接收的查询参数值将自动进行 URL 解码。
使用单一方法处理导航数据
可以通过在接收类上实现 IQueryAttributable
接口来接收导航数据。 IQueryAttributable
接口指定必须由实现类来实现 ApplyQueryAttributes
方法。 此方法具有一个 IDictionary<string, string>
类型的 query
变量,其中包含在导航过程中传递的任何数据。 字典中的每个键都是一个查询参数 id,其值为查询参数值。 使用此方法的优点是可以使用单一方法来处理导航数据,在有多个需要作为整体处理的导航数据项时,这会很有用。
以下示例显示了实现 IQueryAttributable
接口的视图模型类:
public class MonkeyDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
public Animal Monkey { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
// The query parameter requires URL decoding.
string name = HttpUtility.UrlDecode(query["name"]);
LoadAnimal(name);
}
void LoadAnimal(string name)
{
try
{
Monkey = MonkeyData.Monkeys.FirstOrDefault(a => a.Name == name);
OnPropertyChanged("Monkey");
}
catch (Exception)
{
Console.WriteLine("Failed to load animal.");
}
}
...
}
在该示例中,ApplyQueryAttributes
方法从 GoToAsync
方法调用中的 URI 检索 name
查询参数的值。 然后,调用 LoadAnimal
方法检索 Animal
对象,此时它被设置为数据将绑定到的 Monkey
属性的值。
重要
通过 IQueryAttributable
接口接收的查询参数值不会自动进行 URL 解码。
传递和处理多个查询参数
可以通过将多个查询参数与 &
连接来传递它们。 例如,以下代码传递两个数据项:
async void OnCollectionViewSelectionChanged(object sender, SelectionChangedEventArgs e)
{
string elephantName = (e.CurrentSelection.FirstOrDefault() as Animal).Name;
string elephantLocation = (e.CurrentSelection.FirstOrDefault() as Animal).Location;
await Shell.Current.GoToAsync($"elephantdetails?name={elephantName}&location={elephantLocation}");
}
此代码示例在 CollectionView
中检索当前选中的大象,并导航到 elephantdetails
路由,将 elephantName
和 elephantLocation
作为查询参数传递。
要接收多个数据项,对于每个查询参数,可以使用 QueryPropertyAttribute
修饰表示导航到的页面的类或页面的 BindingContext
的类:
[QueryProperty(nameof(Name), "name")]
[QueryProperty(nameof(Location), "location")]
public partial class ElephantDetailPage : ContentPage
{
public string Name
{
set
{
// Custom logic
}
}
public string Location
{
set
{
// Custom logic
}
}
...
}
在此示例中,每个查询参数的类使用 QueryPropertyAttribute
进行修饰。 第一个 QueryPropertyAttribute
指定 Name
属性将接收在 name
查询参数中传递的数据,而第二个 QueryPropertyAttribute
指定 Location
属性将接收在 location
查询参数中传递的数据。 在这两种情况下,查询参数值都是在 GoToAsync
方法调用的 URI 中指定的。
或者,在表示导航到的页面的类或页面的 BindingContext
的类上实现 IQueryAttributable
接口,通过单一方法来处理导航数据:
public class ElephantDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
public Animal Elephant { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
string name = HttpUtility.UrlDecode(query["name"]);
string location = HttpUtility.UrlDecode(query["location"]);
...
}
...
}
在该示例中,ApplyQueryAttributes
方法从 GoToAsync
方法调用中的 URI 检索 name
和 location
查询参数的值。
“后退”按钮行为
通过将 BackButtonBehavior
附加属性设置为 BackButtonBehavior
对象,可以重新定义“后退”按钮的外观和行为。 BackButtonBehavior
类定义以下属性:
Command
属于ICommand
类型,在按下“后退”按钮时执行。CommandParameter
属于object
类型,是传递给Command
的参数。IconOverride
属于ImageSource
类型,是用于“后退”按钮的图标。IsEnabled
属于boolean
类型,指示是否已启用“后退”按钮。 默认值为true
。TextOverride
属于string
类型,是用于“后退”按钮的文本。
所有这些属性都由 BindableProperty
对象提供支持,这意味着这些属性可以作为数据绑定的目标。
下面的代码演示了重新定义“后退”按钮的外观和行为的示例:
<ContentPage ...>
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding BackCommand}"
IconOverride="back.png" />
</Shell.BackButtonBehavior>
...
</ContentPage>
等效 C# 代码如下:
Shell.SetBackButtonBehavior(this, new BackButtonBehavior
{
Command = new Command(() =>
{
...
}),
IconOverride = "back.png"
});
将 Command
属性设置为按下“后退”按钮时执行的 ICommand
,将 IconOverride
属性设置为用于“后退”按钮的图标: