MAUI中Shell.TitleView内容为何每隔一次页面切换就消失?
问题现象
开发了一个带Flyout菜单的MAUI应用,导航栏汉堡按钮旁显示标题和跳转用户设置的按钮。在Android设备/模拟器上,从菜单进入的每隔一个页面,Shell.TitleView里的内容会消失——不管页面选择顺序如何,比如依次打开FirstPage、ThirdPage、SecondPage、FirstPage时,ThirdPage和第二次打开的FirstPage的TitleView都没有内容。用HorizontalStackLayout也会出现同样问题,但直接在Shell.TitleView里放Label就正常,Windows平台完全没问题。切换页面时,Grid内容会短暂正确显示约0.7秒后消失。
原因分析
这是MAUI Shell在Android平台的已知布局复用bug:当使用容器控件(Grid/HorizontalStackLayout)作为TitleView根元素时,Shell在交替加载FlyoutItem对应的页面时,没有正确维护容器的可视状态,子元素会被意外重置或隐藏。单独使用Label时不会触发这个问题,因为Shell对单一元素的状态维护逻辑不同。
解决方案
方案1:强制保持容器可视状态
修改AppShell.xaml中的Grid,添加IsVisible="True",强制控件保持可见:
<Shell.TitleView> <Grid x:Name="titleGrid" ColumnDefinitions="*,*" BackgroundColor="Red" IsVisible="True"> <Label Grid.Column="0" x:Name="title" Style="{StaticResource labelPageTitle}"/> <Button Grid.Column="1" Text="NN" Style="{StaticResource ButtonToProfilePage}" Clicked="ProfileButton_OnClickedAsync"/> </Grid> </Shell.TitleView>
方案2:导航后强制刷新布局
在AppShell.xaml.cs的OnNavigated事件中,添加强制布局刷新的代码:
protected override void OnNavigated(ShellNavigatedEventArgs args) { base.OnNavigated(args); title.Text = Shell.Current.CurrentItem.Title; // 强制刷新TitleView容器布局 titleGrid.ForceLayout(); }
方案3:升级MAUI版本
将MAUI NuGet包升级到最新稳定版,官方后续版本已经修复了多个Shell TitleView相关的Android平台bug。
替代方案
替代方案1:页面单独定义TitleView
不在Shell全局设置TitleView,而是在每个页面的XAML中单独配置:
比如FirstPage.xaml:
<ContentPage ...> <Shell.TitleView> <Grid ColumnDefinitions="*,*" BackgroundColor="Red"> <Label Grid.Column="0" Text="First" Style="{StaticResource labelPageTitle}"/> <Button Grid.Column="1" Text="NN" Style="{StaticResource ButtonToProfilePage}" Clicked="ProfileButton_OnClickedAsync"/> </Grid> </Shell.TitleView> <!-- 页面内容 --> </ContentPage>
这种方式每个页面的TitleView独立,不会受Shell全局复用逻辑影响。
替代方案2:自定义导航栏
隐藏Shell默认导航栏,在页面顶部使用自定义Grid作为导航栏:
- 在AppShell中设置全局隐藏导航栏:
<Shell ... NavBarIsVisible="False"> <!-- FlyoutItems --> </Shell>
- 在每个页面顶部添加自定义导航栏:
<ContentPage ...> <Grid> <!-- 自定义导航栏 --> <Grid ColumnDefinitions="*,*" BackgroundColor="Red" HeightRequest="50"> <Label Grid.Column="0" Text="First" Style="{StaticResource labelPageTitle}" VerticalOptions="Center"/> <Button Grid.Column="1" Text="NN" Style="{StaticResource ButtonToProfilePage}" VerticalOptions="Center"/> </Grid> <!-- 页面内容,Margin避开导航栏 --> <ScrollView Margin="0,50,0,0"> <!-- 内容 --> </ScrollView> </Grid> </ContentPage>
原始代码参考
AppShell.xaml
<?xml version="1.0" encoding="UTF-8" ?> <Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:views="clr-namespace:ProfileApp.Views" x:Class="ProfileApp.AppShell" FlyoutBehavior="Flyout"> <Shell.TitleView> <Grid ColumnDefinitions="*,*" BackgroundColor="Red"> <Label Grid.Column="0" x:Name="title" Style="{StaticResource labelPageTitle}"/> <Button Grid.Column="1" Text="NN" Style="{StaticResource ButtonToProfilePage}" Clicked="ProfileButton_OnClickedAsync"/> </Grid> </Shell.TitleView> <FlyoutItem Title="First" > <ShellContent ContentTemplate="{DataTemplate views:FirstPage}" /> </FlyoutItem> <FlyoutItem Title="Second"> <ShellContent ContentTemplate="{DataTemplate views:SecondPage}" /> </FlyoutItem> <FlyoutItem Title="Third"> <ShellContent ContentTemplate="{DataTemplate views:ThirdPage}" /> </FlyoutItem> </Shell>
AppShell.xaml.cs
using ProfileApp.Views; namespace ProfileApp; public partial class AppShell : Shell { public AppShell() { InitializeComponent(); Routing.RegisterRoute(nameof(ProfilePage), typeof(ProfilePage)); } protected override void OnNavigated(ShellNavigatedEventArgs args) { base.OnNavigated(args); title.Text = Shell.Current.CurrentItem.Title; } private async void ProfileButton_OnClickedAsync(object sender, EventArgs e) { if (Shell.Current.CurrentPage is ProfilePage) return; await Shell.Current.GoToAsync(nameof(ProfilePage), true); title.Text = "Profile Settings"; } }
MauiProgram.cs
public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }); var services = builder.Services; services.AddTransient<AppShell>(); services.AddTransient<FirstPage>(); services.AddTransient<SecondPage>(); services.AddTransient<ThirdPage>(); services.AddTransient<ProfilePage>(); return builder.Build(); }
内容的提问来源于stack exchange,提问作者Frank Mehlhop

