MAUI的Shell导航框架,也是以路由方式进行导航,并提供了两套导航方式,一是如前面章节所述的视觉层次结构,会自动建立导航路由,可以进行不同层次页面的导航切换;二是为页面手动注册路由,并执行代码导航。
一、注册路由
1、视觉层次结构页面的路由注册
在视觉层次结构中(Shell > FlyoutItem|TabBar > Tab > ShellContent),框架自动建立导航路由,可以直接进行导航切换。如果要通过代码导航到视觉层次页面,可以在定义视觉层次结构时,显示的定义路由,如:
[/span>Shell
......
[/span>FlyoutItem Route="animals"
[/span>Tab Title="Domestic" Route="domestic"
[/span>ShellContent
//代码效果参考:http://www.jhylw.com.cn/510735869.html
Title="Cats"ContentTemplate="{DataTemplate view:Cats}"
Route="cats" />
[/span>ShellContent
Title="Dogs"
ContentTemplate="{DataTemplate view:Dogs}"
Route="dogs" />
[/span>ShellContent
Title="Monkeys"
ContentTemplate="{DataTemplate view:Monkeys}"
Route="monkeys" />
[/span>ShellContent
Title="Bears"
ContentTemplate="{DataTemplate view:Bears}"
Route="bears" />
[/span>ShellContent
Title="Elephants"
ContentTemplate="{DataTemplate view:Elephants}"
Route="elephants" />
[/span>ShellContent
Title="About"
ContentTemplate="{DataTemplate view:About}"
Route="about" />
animals
domestic
cats
dogs
monkeys
bears
elephants
about
2、非视觉层次结构页面的路由注册
MAUI中还有很多页面,没有在视觉层次结构中定义,这些页面需要进行手动注册路由,一般在AppShell后台代码的构造函数中进行。
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
//注册非视觉层次页面路由,所有路由名称需保持唯一,它们是全局的
//当导航到视觉层次结构中的路由时,并不会创建导航堆栈;但导航到非视觉层次页面路由时,创建导航堆
Routing.RegisterRoute("dogdetail",typeof(DogDetail));
Routing.RegisterRoute("monkeydetail", typeof(MonkeyDetail));
//也可以将路由注册到不同的视觉层次结构上,可以实现跟踪当前路由层次,导航到不同的页面
//如下例中,都是导航到detail,但在dogs路由层次结构中时,将导航到DogDetail;反之,将导航到MonkeyDetail
Routing.RegisterRoute("dogs/detail", typeof(DogDetail));
Routing.RegisterRoute("monkeys/detail", typeof(MonkeyDetail));
}
}
二、执行导航
1、在视觉层次结构中,可以直接执行导航跳转。注:这些跳转页面,不会进入到导航堆栈中。
2、非视觉层次结构的页面,需要通过编程式导航来实现。
//下例为Monkeys页面,通过按钮点击事件进行导航
public partial class Monkeys : ContentPage
{
......
private async void Button_Clicked(object sender, EventArgs e)
{
//Shell.Current获得对当前Shell对象的引用,等效于((Shell)App.Current.MainPage)
//Shell对象包括了GoToAsync导航方法,以及CurrentItem、CurrentPage、CurrentState、BackButtonBehavior等属性
await Shell.Current.GoToAsync("dogdetail");
await ((Shell)App.Current.MainPage).GoToAsync("dogdetail");
await DisplayAlert("显示当前路由", Shell.Current.CurrentState.Location.ToString(), "OK");
}
}
3、绝对路由和相对路由
视觉层次结构的页面,可以通过绝对路径进行导航,如【Shell.Current.GoToAsync("//animals/domestic/dogs")】
非视觉层次结构的页面,可以通过相对路径进行导航,如【Shell.Current.GoToAsync("dogdetail")】
上下文导航。注册非视觉层次结构页面时,将路由注册到结构层次页面上,如Routing.RegisterRoute("monkeys/detail"...)。当在Monkeys页面中,导航到detail时,会匹配monkeys/detail。
向后导航。向后导航,【await Shell.Current.GoToAsync("..")】;向后导航与路由导航结合,【await Shell.Current.GoToAsync("../route")】;可以多次向后导航,【await Shell.Current.GoToAsync("../../route")】
关于路由导航,还有一些概念比较模糊,建议暂时按以上两种方式来导航
三、导航传参
1、传递参数。
1)方法一:查询字符串
//传递字符串:直接通过查询字符串传递参数
//传递一个字符串参数
await Shell.Current.GoToAsync("monkeydetail?name=sun");
//传递多个字符串参数
await Shell.Current.GoToAsync("monkeydetail?name=sun&sex=male");
//也可以使用内插值变量
string name = "sun"
await Shell.Current.GoToAsync($"monkeydetail?name={name}");
2)方法一:字典类型参数
//传递对象:通过GoToAsync方法的第二个参数,IDictionary字典类型对象传参
//传递一个对象
var p1 = new Person{Name="zs",Age=18};
var navigationParam = new Dictionary[span style="color: rgba(0, 0, 255, 1)">string, object
{
{ "person1", p1 }
};
await Shell.Current.GoToAsync($"persondetail", navigationParame);
//传递多个对象
var p1 = new Person{Name="zs",Age=18};
var p2 = new Person{Name="ls",Age=28};
var navigationParam = new Dictionary[span style="color: rgba(0, 0, 255, 1)">string, object
{
{ "person1", p1 }
{ "person2", p2 }
};
await Shell.Current.GoToAsync($"persondetail", navigationParame);
2、接收参数。
1)方法一:通过QueryProperty特性,接收路由参数
//QueryProperty特性
//QueryProperty特性的第一个参数为接收路由参数的属性,第一个参数为路由参数的键名
//通过查询参数传递的路由参数,会自动转为键值对形式
//注意接收路由参数的属性,必须是可观察属性(MVVM),触发PropertyChanged事件
【QueryProperty(nameof(Name), "name")】
【QueryProperty(nameof(Sex), "sex")】
public partial class MonkeyDetail : ContentPage
{
public MonkeyDetail()
{
InitializeComponent();
BindingContext = this; //将BindingContext设置为当前对象
}
//定义可观察属性Name来接收键名为name的路由参数
private string name;
public string Name
{
get { return name; }
set
{
name = value;
OnPropertyChanged();
}
}
//定义可观察属性Sex来接收键名为sex的路由参数
private string sex;
public string Sex
{
get { return sex; }
set
{
sex = value;
OnPropertyChanged();
}
}
}
//XAML文件中使用路由参数
[span style="color: rgba(0, 0, 0, 1)">ContentPage
......
[span style="color: rgba(0, 0, 0, 1)">Label
HorizontalOptions="Center"
Text="{Binding Name}"
VerticalOptions="Center" />
[span style="color: rgba(0, 0, 0, 1)">Label
HorizontalOptions="Center"
Text="{Binding Sex}"
VerticalOptions="Center" />
2)方法二:通过实现IQueryAttributable接口,接收路由参数(在ViewModel中使用)
//接收字符串路由参数
public class PersonDetailViewModel : IQueryAttributable, ObservableObject
{
【ObservableProperty】
private string name;
【ObservableProperty】
private string sex;
public void ApplyQueryAttributes(IDictionary[span style="color: rgba(0, 0, 255, 1)">string, object
{
name = HttpUtility.UrlDecode(query【"name"】.ToString());
sex = HttpUtility.UrlDecode(query【"sex"】.ToString());
}
}
//接收对象类型参数
public class PersonDetailViewModel : IQueryAttributable, INotifyPropertyChanged
{
【ObservableProperty】
private Person person;
public void ApplyQueryAttributes(IDictionary[span style="color: rgba(0, 0, 255, 1)">string, object
{
person = query【"p1"】 as Person;
}
...
}
四、路由守卫
1、全局守卫:类似于生命周期函数,Shell类提供OnNavigating(导航发生前)和OnNavigated(导航发生后)可重写方法,通过重写这两个方法,可以对导航进行控制,实现路由守卫的功能。OnNavigating和OnNavigated方法,在AppShell.xaml.cs中定义。
//OnNavigating和OnNavigated方法的常用属性和方法
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
......
}
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
ShellNavigationState current = args.Current;//当前页,可通过Location属性获取URL
ShellNavigationState target = args.Target; //目标页,可通过Location属性获取URL
ShellNavigationSource source = args.Source; //导航类型,枚举,包括Unknown/Push/Pop/PopToRoot/Insert/Remove/ShellItemChanged/ShellSectionChanged/ShellContentChanged
bool canCancle = args.CanCancel; //是否可以取消
bool canclled = args.Cancelled; //是否取消
args.Cancel(); //取消导航
//获取导航令牌,并完成导航
ShellNavigatingDeferral token = args.GetDeferral();
token.Complete();
}
protected override void OnNavigated(ShellNavigatedEventArgs args)
{
base.OnNavigated(args);
ShellNavigationState current = args.Current; //当前页
ShellNavigationState previous = args.Previous; //上一页
ShellNavigationSource source = args.Source; //导航类型
}
}
//案例一:取消各后导航
protected override void OnNavigating(ShellNavigatingEventArgs args)
{
base.OnNavigating(args);
if (args.Source == ShellNavigationSource.Pop)
{
args.Cancel();
}
}