MAUI Shell侧滑菜单点击无法正确导航至目标页面
MAUI AppShell Flyout导航异常问题
将Xamarin应用迁移到MAUI后,点击AppShell侧滑菜单(Flyout)选项时无法跳转到目标页面,始终停留在最后访问的页面,相同逻辑在Xamarin中正常。应用启动逻辑:在App.xaml.cs中,若自动登录勾选或登录验证成功,设置MainPage为AppShell,否则设为LoginPage。
AppShell.xaml
<Shell x:Class="SnyderMobile.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:pages="clr-namespace:SnyderMobile.Views" xmlns:controls="clr-namespace:SnyderMobile.Controls" Shell.FlyoutBehavior="Flyout" BackgroundColor="#FAFAFA" Shell.ForegroundColor="#5E5D5D" Shell.TitleColor="#5E5D5D"> <Shell.FlyoutBackdrop> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="White" Offset="0.1"/> <GradientStop Color="#E786A8" Offset="0.6"/> <GradientStop Color="#BC1538" Offset="1.0" /> </LinearGradientBrush> </Shell.FlyoutBackdrop> <Shell.FlyoutHeader> <Grid BackgroundColor="Black"> <Image Aspect="AspectFill" Source="background_shell.png" Opacity="0.6" /> <StackLayout Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="Center"> <Frame HeightRequest="80" WidthRequest="80" CornerRadius="50" VerticalOptions="Center" HorizontalOptions="Center"> <Label Text="{Binding FirstLetter}" FontAttributes="Bold" FontSize="Large" TextColor="#BC1538" VerticalOptions="Center" HorizontalOptions="Center" Padding="0" Margin="0"/> </Frame> <StackLayout Margin="0, 0, 0, 0" VerticalOptions="Center" HorizontalOptions="Center" Spacing="2"> <Label Text="{Binding Welcome}" FontAttributes="Bold" FontSize="Large" TextColor="White" Margin="10,0,0,0" VerticalOptions="Center"/> <StackLayout Orientation="Horizontal" Margin="10,0,0,0" IsVisible="{Binding LocationIsVisible}"> <Image Source="locate_icon_white" HeightRequest="25" Aspect="AspectFit" Margin="0" /> <Label Text="{Binding Location}" FontAttributes="Bold" FontSize="Default" TextColor="White" VerticalOptions="Center"/> </StackLayout> </StackLayout> </StackLayout> </Grid> </Shell.FlyoutHeader> <Shell.FlyoutFooter> <controls:FlyoutFooter /> </Shell.FlyoutFooter> <FlyoutItem Shell.TabBarIsVisible="False" FlyoutDisplayOptions="AsMultipleItems"> <ShellContent Title="Change Working Location" Icon="navigation.png" ContentTemplate="{DataTemplate pages:LocationPage}" /> <ShellContent Title="Main Menu" Icon="home_grey.png" ContentTemplate="{DataTemplate pages:AboutPage}" /> </FlyoutItem> </Shell>
AppShell.xaml.cs
public partial class AppShell : Shell { private AppShellViewModel _viewModel; public Dictionary<string, Type> Routes { get; private set; } = new Dictionary<string, Type>(); public AppShell() { InitializeComponent(); BindingContext = _viewModel = new AppShellViewModel(); PropertyChanged += Shell_PropertyChanged; } private void Shell_PropertyChanged(object sender, PropertyChangedEventArgs e) { _viewModel.UpdateShell(e); } }
AppShellViewModel.cs
public class AppShellViewModel : BaseViewModel { private string _welcome; public string Welcome { get => _welcome; set { _welcome = value; OnPropertyChanged(nameof(Welcome)); } } private string _firstLetter; public string FirstLetter { get => _firstLetter; set { _firstLetter = value; OnPropertyChanged(nameof(FirstLetter)); } } private string _location; public string Location { get => _location; set { _location = value; OnPropertyChanged(nameof(Location)); } } private bool _locationIsVisible; public bool LocationIsVisible { get => _locationIsVisible ; set { _locationIsVisible = value; OnPropertyChanged(nameof(LocationIsVisible)); } } public AppShellViewModel() { Welcome = "Hello, " + "Test"; FirstLetter = "T"; } public void UpdateShell(PropertyChangedEventArgs e) { if (e.PropertyName.Equals("FlyoutIsPresented")) { if (GlobalVariables.LoginAuth.Location != null) { LocationIsVisible = true; Location = "Test"; } } } }
LocationViewModel.cs
public class LocationViewModel : BaseViewModel { #region Properties private List<JsonLocation> _locations; public List<JsonLocation> Locations { get => _locations; set { _locations = value; OnPropertyChanged(nameof(Locations)); } } private JsonLocation _location; public JsonLocation Location { get => _location; set { _location = value; OnPropertyChanged(nameof(Location)); } } public ICommand Tapped_Command { get; set; } #endregion public LocationViewModel(INavigation navigation) { Navigation = navigation; Title = "Working Locations"; Tapped_Command = new Command(List_Tapped); } public async void List_Tapped() { await Navigation.PushAsync(new AboutPage()); } }
App.xaml.cs (主页逻辑)
private async void SetMainPageAsync() { if (autoLogin) { try { var resp = CheckLogin(userName); if (resp.Success) { MainPage = new AppShell(); } else { MainPage = new LoginPage(); } } catch (Exception e) { MainPage = new LoginPage(); } } else { MainPage = new LoginPage(); } }
问题原因及解决方案
核心原因
MAUI Shell的导航机制与Xamarin存在差异:通过Navigation.PushAsync手动添加页面到导航栈后,Flyout切换时不会自动重置导航栈,导致停留在栈顶页面;同时ShellContent的懒加载特性会保留导航栈状态,加剧该问题。
修复步骤
替换手动导航为Shell原生路由导航
修改LocationViewModel中的List_Tapped方法,使用Shell路由代替PushAsync:public async void List_Tapped() { // 移除原代码:await Navigation.PushAsync(new AboutPage()); await Shell.Current.GoToAsync("//MainMenu"); }注册Shell路由并配置
ShellContent
在AppShell构造函数中添加路由注册:public AppShell() { InitializeComponent(); BindingContext = _viewModel = new AppShellViewModel(); PropertyChanged += Shell_PropertyChanged; // 注册路由 Routing.RegisterRoute("LocationPage", typeof(LocationPage)); Routing.RegisterRoute("MainMenu", typeof(AboutPage)); }同时在
FlyoutItem的ShellContent中添加Route属性:<FlyoutItem Shell.TabBarIsVisible="False" FlyoutDisplayOptions="AsMultipleItems"> <ShellContent Route="LocationPage" Title="Change Working Location" Icon="navigation.png" ContentTemplate="{DataTemplate pages:LocationPage}" /> <ShellContent Route="MainMenu" Title="Main Menu" Icon="home_grey.png" ContentTemplate="{DataTemplate pages:AboutPage}" /> </FlyoutItem>重置导航栈(可选)
若需要切换Flyout时完全清空导航栈,可在AppShell中监听项选择事件:public AppShell() { InitializeComponent(); // ... 其他初始化代码 ItemSelected += AppShell_ItemSelected; } private async void AppShell_ItemSelected(object sender, ItemSelectedEventArgs e) { if (e.Item is ShellContent shellContent && !string.IsNullOrEmpty(shellContent.Route)) { await Shell.Current.GoToAsync($"//{shellContent.Route}", true); } }修正
BaseViewModel的导航上下文
确保BaseViewModel中的导航属性使用Shell的全局导航上下文,而非构造函数传入的INavigation:public class BaseViewModel : INotifyPropertyChanged { public INavigation Navigation => Shell.Current.Navigation; // ... 其他属性和方法 }
内容的提问来源于stack exchange,提问作者Sashi Kumar
相关产品推荐
相关产品推荐

