MAUI Shell导航:如何统一回退到指定Products页面?
我需要实现用户完成产品移动操作后,统一返回至Products页面(用户大概率会继续从同一位置移动产品)。目前存在两种导航场景:
场景1
当前路由:
//SourceAisle/SourceLocation/Products/TargetAisle/TargetLocation
场景2
当前路由:
//SourceAisle/SourceLocation/Products/FavoriteLocation
两种场景下,操作完成后都需要返回至:
//SourceAisle/SourceLocation/Products
目前可以在场景1用Shell.Current.GoToAsync("../.."),场景2用Shell.Current.GoToAsync(".."),但希望用统一逻辑直接回退到Products页面,不想推送新页面,只需要弹出所有页面直到目标页面显示。
官方文档提到可以用/route或///route进行反向搜索,但不确定具体用法。之前在旧Xamarin项目中写过扩展方法实现类似功能,但更倾向于用Shell原生方案。
备注:旧Xamarin扩展方法实现
// await Shell.Current.Navigation.PopToAsync<ProductsPage>(); internal static class NavigationExtensions { private static readonly SemaphoreSlim _semaphore = new (1, 1); public static async Task PopToAsync<T>(this INavigation nav, Page nextPage = null, bool animated = true) where T : Page { await _semaphore.WaitAsync(); try { bool exists = nav.NavigationStack .Where((p, i) => i <= nav.NavigationStack.Count - 2) .Any(p => p is T); if (!exists) throw new ArgumentException($"The specified page {typeof(T).Name} does not exist in the stack"); for (var index = nav.NavigationStack.Count - 2; index > 0; index--) { if (nav.NavigationStack[index] is T) break; nav.RemovePage(nav.NavigationStack[index]); } if (nextPage is not null) nav.InsertPageBefore(nextPage, nav.NavigationStack[nav.NavigationStack.Count - 1]); await nav.PopAsync(animated); } finally { _semaphore.Release(); } } }
补充信息:AppShell配置
AppShell.xaml
<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="MyApp.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:views="clr-namespace:MyApp.Views"> <FlyoutItem Route="SourceAisle" Title="Stock Movement"> <ShellContent ContentTemplate="{DataTemplate views:AislesPage}" /> </FlyoutItem> </Shell>
AppShell.xaml.cs
public partial class AppShell : Shell { public AppShell() { InitializeComponent(); Routing.RegisterRoute("SourceAisle", typeof(AislesPage)); Routing.RegisterRoute("SourceLocation", typeof(LocationsPage)); Routing.RegisterRoute("Products", typeof(ProductsPage)); Routing.RegisterRoute("TargetAisle", typeof(AislesPage)); Routing.RegisterRoute("TargetLocation", typeof(LocationsPage)); Routing.RegisterRoute("FavoriteLocation", typeof(FavoritesPage)); } }
注:aisle和location页面同时用于源位置和目标位置,通过状态区分场景,此部分不在本次问题范围内。当前导航调用:从LocationsViewModel中调用await Shell.Current.GoToAsync("Products"),从ProductsViewModel中调用await Shell.Current.GoToAsync("TargetAisle")等,认为是相对路由,但不确定是否需要调整。
可以通过MAUI Shell的原生路由语法实现统一回退需求,推荐以下两种方案:
方案1:使用///Products反向匹配路由
Shell的///route语法会从当前导航栈的最顶层开始,反向查找第一个匹配指定路由名的页面,找到后自动弹出该页面之上的所有页面,直接回到目标页面,完全符合你“不推送新页面、仅回退到已存在页面”的需求。
两种场景下统一调用即可:
await Shell.Current.GoToAsync("///Products");
此方案灵活性强,即便后续路由层级调整,只要Products页面的路由名不变,就能正常匹配。
方案2:使用绝对路由指定完整路径
如果Products页面的父路由结构固定(SourceAisle/SourceLocation/Products),也可以直接使用绝对路由跳转:
await Shell.Current.GoToAsync("//SourceAisle/SourceLocation/Products");
但这种方案依赖固定的路由层级,后续结构调整时需要同步修改代码,适配性不如方案1。
额外说明
你当前使用的相对路由调用(如GoToAsync("Products"))是正确的,无需调整,不会影响反向匹配的逻辑。只要Products页面的路由注册(Routing.RegisterRoute("Products", typeof(ProductsPage)))保持正确,///Products就能准确匹配到栈中的目标页面。
内容的提问来源于stack exchange,提问作者Arthur Rey

