You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的懒加载特性会保留导航栈状态,加剧该问题。

修复步骤

  1. 替换手动导航为Shell原生路由导航
    修改LocationViewModel中的List_Tapped方法,使用Shell路由代替PushAsync:

    public async void List_Tapped()
    {
        // 移除原代码:await Navigation.PushAsync(new AboutPage());
        await Shell.Current.GoToAsync("//MainMenu");
    }
    
  2. 注册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>
    
  3. 重置导航栈(可选)
    若需要切换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);
        }
    }
    
  4. 修正BaseViewModel的导航上下文
    确保BaseViewModel中的导航属性使用Shell的全局导航上下文,而非构造函数传入的INavigation:

    public class BaseViewModel : INotifyPropertyChanged
    {
        public INavigation Navigation => Shell.Current.Navigation;
        // ... 其他属性和方法
    }
    

内容的提问来源于stack exchange,提问作者Sashi Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 12:39:50