关于 maui-safe-area
面向 .NET MAUI 开发者的安全区域与边到边布局指导,介绍 .NET 10+ 的 `SafeAreaEdges` API。内容涵盖逐边控制、键盘避让及旧 API 迁移。
主要功能
- 01按边配置安全区域
- 02构建边到边布局
- 03处理键盘插入区域
- 04协调 Blazor Hybrid CSS 插入区域
- 05迁移旧安全区域 API
适用场景
- 01使表单避开系统栏和键盘
- 02构建沉浸式照片或视频页面
- 03创建支持键盘避让的聊天布局
- 04升级到 .NET 10 安全区域处理方式
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dotnet/ skills --skill maui-safe-area安装后在 Claude Code 中输入/maui-safe-area确认已加载。
附带文件references/safe-area-api.md
详细介绍
安全区域与全面屏布局(.NET 10+)
.NET 10 推出了全新的跨平台安全区域 API,取代旧版仅适用于 iOS 的 UseSafeArea 和布局级 IgnoreSafeArea 属性。新的 SafeAreaEdges 属性和 SafeAreaRegions 标志枚举,可通过统一的 API 为 Android、iOS 和 Mac Catalyst 上的各个控件分别管理各边的安全区域。
这是 .NET 10 中新增的 API。 如果项目面向 .NET 9 或更早版本,这些 API 并不存在。请引导开发者改用旧版
ios:Page.UseSafeArea和Layout.IgnoreSafeArea属性。
适用场景
- 升级到 .NET 10 后,内容与状态栏、刘海、灵动岛或主屏幕指示条重叠
- 实现全面屏/沉浸式布局(照片查看器、视频播放器、地图)
- 为聊天或表单 UI 实现键盘避让
- 从
ios:Page.UseSafeArea、Layout.IgnoreSafeArea或WindowSoftInputModeAdjust.Resize迁移 - 需要协调 CSS
env(safe-area-inset-*)的 Blazor Hybrid 应用 - 混合布局:标题栏延伸至屏幕边缘,而主体遵循安全区域
不适用场景
- 面向 .NET 9 或更早版本的项目——请使用旧版 iOS 专用 API
- 与系统栏或键盘无关的一般页面布局问题——请参考标准布局指南
- 应用生命周期或导航结构——请参考 maui-app-lifecycle 或 Shell 指南
- 主题或视觉样式——请使用 maui-theming 技能
输入
- 目标框架:使用新 API 必须为
net10.0-*或更高版本 - 目标平台:Android、iOS、Mac Catalyst(Windows 没有系统栏内边距)
- UI 方案:XAML/C#、Blazor Hybrid 或 MauiReactor
SafeAreaRegions 枚举
[Flags]
public enum SafeAreaRegions
{
None = 0, // Edge-to-edge — no safe area padding
SoftInput = 1 << 0, // Pad to avoid the on-screen keyboard
Container = 1 << 1, // Stay inside status bar, notch, home indicator
Default = -1, // Use the platform default for the control type
All = 1 << 15 // Respect all safe area insets (most restrictive)
}
SoftInput 和 Container 是可组合标志:
SafeAreaRegions.Container | SafeAreaRegions.SoftInput = 同时遵循系统栏和键盘。
SafeAreaEdges 结构体
public readonly struct SafeAreaEdges
{
public SafeAreaRegions Left { get; }
public SafeAreaRegions Top { get; }
public SafeAreaRegions Right { get; }
public SafeAreaRegions Bottom { get; }
// Uniform — same value for all four edges
public SafeAreaEdges(SafeAreaRegions uniformValue)
// Horizontal / Vertical
public SafeAreaEdges(SafeAreaRegions horizontal, SafeAreaRegions vertical)
// Per-edge
public SafeAreaEdges(SafeAreaRegions left, SafeAreaRegions top,
SafeAreaRegions right, SafeAreaRegions bottom)
}
静态预设:SafeAreaEdges.None、SafeAreaEdges.All、SafeAreaEdges.Default。
XAML 类型转换器
采用类似 Thickness 的逗号分隔语法:
SafeAreaEdges="Container"
SafeAreaEdges="Container, SoftInput"
SafeAreaEdges="Container, Container, Container, SoftInput"
控件默认值
| 控件 | 默认值 | 说明 |
|---|---|---|
ContentPage | None | 全面屏布局。这是相较于 .NET 9 在 Android 上的破坏性变更。 |
Layout(Grid、StackLayout 等) | Container | 避开系统栏/刘海,但会延伸至键盘下方 |
ScrollView | Default | iOS 会映射为自动内容内边距。只有 Container 和 None 会生效。 |
ContentView | None | 继承父级行为 |
Border | None | 继承父级行为 |
相较于 .NET 9 的破坏性变更
ContentPage 的默认值变为 None
在 .NET 9 中,Android 上的 ContentPage 行为类似于 Container。在 .NET 10 中,所有平台的默认值都是 None。如果升级后 Android 内容延伸到了状态栏下方:
<ContentPage>
<ContentPage SafeAreaEdges="Container">
WindowSoftInputModeAdjust.Resize 已被取代
WindowSoftInputModeAdjust.Resize 仍然存在且仍可编译(并未移除,也未标记为过时),但它仅适用于 Android。要实现跨平台键盘避让,建议在 ContentPage 上使用 SafeAreaEdges="All"(或 SoftInput 区域)。
使用模式
全面屏沉浸式内容
在页面和布局上都设置为 None——布局默认值为 Container:
<ContentPage SafeAreaEdges="None">
<Grid SafeAreaEdges="None">
<Image Source="background.jpg" Aspect="AspectFill" />
<VerticalStackLayout Padding="20" VerticalOptions="End">
<Label Text="Overlay text" TextColor="White" FontSize="24" />
</VerticalStackLayout>
</Grid>
</ContentPage>
表单和关键内容
<ContentPage SafeAreaEdges="All">
<VerticalStackLayout Padding="20">
<Label Text="Safe content" FontSize="18" />
<Entry Placeholder="Enter text" />
<Button Text="Submit" />
</VerticalStackLayout>
</ContentPage>
感知键盘的聊天布局
<ContentPage>
<Grid RowDefinitions="*,Auto"
SafeAreaEdges="Container, Container, Container, SoftInput">
<ScrollView Grid.Row="0">
<VerticalStackLayout Padding="20" Spacing="10">
<Label Text="Messages" FontSize="24" />
</VerticalStackLayout>
</ScrollView>
<Border Grid.Row="1" BackgroundColor="LightGray" Padding="20">
<Grid ColumnDefinitions="*,Auto" Spacing="10">
<Entry Placeholder="Type a message..." />
<Button Grid.Column="1" Text="Send" />
</Grid>
</Border>
</Grid>
</ContentPage>
混合布局:全面屏标题栏 + 安全区域主体 + 键盘页脚
<ContentPage SafeAreaEdges="None">
<Grid RowDefinitions="Auto,*,Auto">
<Grid BackgroundColor="{StaticResource Primary}">
<Label Text="App Header" TextColor="White" Margin="20,40,20,20" />
</Grid>
<ScrollView Grid.Row="1" SafeAreaEdges="Container">
<VerticalStackLayout Padding="20">
<Label Text="Main content" />
</VerticalStackLayout>
</ScrollView>
<Grid Grid.Row="2" SafeAreaEdges="SoftInput"
BackgroundColor="LightGray" Padding="20">
<Entry Placeholder="Type a message..." />
</Grid>
</Grid>
</ContentPage>
以编程方式设置(C#)
var page = new ContentPage
{
SafeAreaEdges = SafeAreaEdges.All
};
var grid = new Grid
{
SafeAreaEdges = new SafeAreaEdges(
left: SafeAreaRegions.Container,
top: SafeAreaRegions.Container,
right: SafeAreaRegions.Container,
bottom: SafeAreaRegions.SoftInput)
};
决策指南
| 场景 | SafeAreaEdges 值 |
|---|---|
| 表单、关键输入项 | All |
| 照片查看器、视频播放器、游戏 | None(页面和布局上都设置) |
| 带固定标题栏/页脚的可滚动内容 | Container |
| 带底部输入栏的聊天/消息界面 | 按边设置:Container, Container, Container, SoftInput |
| Blazor Hybrid 应用 | 页面设为 None;使用 CSS env() 设置内边距 |
Blazor Hybrid 集成
对于 Blazor Hybrid 应用,请使用 CSS 处理安全区域,以避免重复添加内边距。
- 页面保持边缘到边缘显示(.NET 10 中的默认设置):
<ContentPage SafeAreaEdges="None">
<BlazorWebView HostPage="wwwroot/index.html">
<BlazorWebView.RootComponents>
<RootComponent Selector="#app" ComponentType="{x:Type local:Routes}" />
</BlazorWebView.RootComponents>
</BlazorWebView>
</ContentPage>
- 在
index.html中添加viewport-fit=cover:
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
- 使用 CSS
env()函数:
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
可用的 CSS 环境变量:env(safe-area-inset-top)、env(safe-area-inset-bottom)、env(safe-area-inset-left)、env(safe-area-inset-right)。
从旧版 API 迁移
| 旧版(.NET 9 及更早版本) | 新版(.NET 10+) |
|---|---|
ios:Page.UseSafeArea="True" | SafeAreaEdges="Container" |
Layout.IgnoreSafeArea="True" | SafeAreaEdges="None" |
WindowSoftInputModeAdjust.Resize | ContentPage 上的 SafeAreaEdges="All" |
旧版 ios:Page.UseSafeArea 和 Layout.IgnoreSafeArea 属性仍可编译,但已标记为过时。IgnoreSafeArea="True" 在内部会映射到 SafeAreaRegions.None。WindowSoftInputModeAdjust.Resize 并未过时——它仍受支持,但仅适用于 Android。
<ContentPage xmlns:ios="clr-namespace:Microsoft.Maui.Controls.PlatformConfiguration.iOSSpecific;assembly=Microsoft.Maui.Controls"
ios:Page.UseSafeArea="True">
<ContentPage SafeAreaEdges="Container">
平台特定行为
iOS 和 Mac Catalyst
- 安全区域内边距涵盖:状态栏、导航栏、标签栏、刘海屏/灵动岛、主屏幕指示条
- 键盘显示时,
SoftInput会包含键盘 - 旋转设备或 UI 可见性变化时,内边距会自动更新
ScrollView搭配Default时,会映射到UIScrollViewContentInsetAdjustmentBehavior.Automatic
让导航栏透明,以便内容显示在导航栏后方:
<Shell Shell.BackgroundColor="#80000000" Shell.NavBarHasShadow="False" />
Android
- 安全区域内边距涵盖:系统栏(状态栏/导航栏)和显示屏挖孔
SoftInput会包含软键盘- MAUI 内部使用
WindowInsetsCompat和WindowInsetsAnimationCompat - 行为会因 Android 版本和 OEM 的边缘到边缘显示设置而异
常见问题
-
忘记同时在布局上设置
None。ContentPage SafeAreaEdges="None"会使页面边缘到边缘显示,但子布局默认使用Container,仍会向内添加内边距。若要实现真正的沉浸式内容,请在页面和布局上都设置None。 -
直接在 ScrollView 上使用
SoftInput。 ScrollView 会管理自身的内容内边距,并忽略SoftInput。请将 ScrollView 包装在 Grid 或 StackLayout 中,并在包装布局上应用SoftInput。 -
混淆
Default和None。Default表示“此控件类型的平台默认设置”——在 iOS 上的 ScrollView 中,这会启用自动内容内边距。None表示“完全不添加安全区域内边距”。 -
Blazor Hybrid 中重复添加内边距。 在页面上设置
SafeAreaEdges="Container",同时使用 CSSenv(safe-area-inset-*),会导致内边距加倍。请选择一种方式——建议 Blazor 使用 CSS。 -
Blazor 中缺少
viewport-fit=cover。 若没有此 meta 标签,iOS 上的 CSSenv(safe-area-inset-*)值始终为零。 -
假定 Android 行为与 .NET 9 相同。 升级到 .NET 10 后,Android 上的
ContentPage默认值为None(以前实际上为Container)。若要恢复先前的行为,请添加SafeAreaEdges="Container"。 -
在新代码中使用旧版
ios:Page.UseSafeArea。 旧 API 仅适用于 iOS,且已过时。请始终使用SafeAreaEdges管理跨平台安全区域。
检查清单
- Android 升级:如果内容延伸到状态栏下方,则添加
SafeAreaEdges="Container" - 边缘到边缘显示:在页面和布局上都设置
None - ScrollView 键盘避让使用包装 Grid,而不是 ScrollView 自身的
SafeAreaEdges - Blazor Hybrid:使用 XAML 或 CSS 安全区域,不要两者兼用
- 在 Blazor 的
index.html<meta viewport>标签中设置viewport-fit=cover - 将旧版
UseSafeArea/IgnoreSafeArea迁移到SafeAreaEdges