📌 前言:为什么选择 .NET MAUI?
在跨平台开发领域,Flutter、React Native、Electron 等框架早已百花齐放。而微软推出的 .NET MAUI(.NET Multi-platform App UI),作为 Xamarin.Forms 的继任者,正以"一套代码、四端运行"的能力,悄然成为 .NET 生态中不可或缺的一环。
- ✅ 原生性能:非 WebView,直接调用各平台原生控件
- ✅ 统一语言:C# + XAML,无需学习 Dart/JS/TS
- ✅ 深度集成:与 ASP.NET Core、Entity Framework、Azure 无缝协作
- ✅ 开源免费:Apache 2.0 许可,GitHub 活跃维护
然而,许多开发者反馈:官方文档碎片化、概念跳跃、缺乏实战引导。本文将弥补这一空白,以"构建一个真实 App"为主线,系统讲解 .NET MAUI 的核心知识点,助你从"能跑"到"精通"。
第一章:环境搭建与项目结构全景
1.1 开发环境准备
必备组件(Windows)
- Visual Studio 2022(17.3+)
- 安装时勾选 ".NET Multi-platform App UI development" 工作负载
- .NET SDK 8.0(推荐)
- Android SDK / Emulator(用于 Android 调试)
- 可选:Mac 配对(用于 iOS 编译)
💡 验证安装:
dotnet --list-sdks # 应包含 8.0.x
dotnet workload list # 应包含 maui
若未安装 MAUI 工作负载:
dotnet workload install maui
1.2 创建第一个项目
dotnet new maui -n MyMauiApp
cd MyMauiApp
dotnet build
或通过 VS 2022 → "创建新项目" → 搜索 ".NET MAUI App"。
1.3 项目结构详解
MyMauiApp/
├── App.xaml / App.xaml.cs ← 应用入口(类似 Main)
├── AppShell.xaml / AppShell.xaml.cs← Shell 导航容器(默认启用)
├── MainPage.xaml ← 主页面
├── Platforms/ ← 平台专属代码
│ ├── Android/ ← AndroidManifest.xml, MainActivity
│ ├── iOS/ ← Info.plist, AppDelegate
│ ├── MacCatalyst/
│ └── Windows/ ← Package.appxmanifest
├── Resources/ ← 资源文件
│ ├── Styles/ ← Colors.xaml, Styles.xaml(全局样式)
│ ├── Images/ ← 图片资源(自动适配 DPI)
│ └── Raw/ ← 原始文件(如 JSON、SQLite DB)
├── MauiProgram.cs ← 启动配置(依赖注入、服务注册)
└── MyMauiApp.csproj ← 项目文件(含 TargetFrameworks)
🔑 设计哲学:
- 共享代码写在根目录
- 平台差异通过
Platforms/下的 partial class 实现
第二章:XAML 与 UI 构建体系
2.1 XAML 是什么?
XAML(eXtensible Application Markup Language)是一种基于 XML 的声明式语言,用于定义 UI 结构。在编译时,XAML 会被转换为 C# 代码,通过 InitializeComponent() 加载。
<Label Text="Hello MAUI!" FontSize="24" />
等价于:
var label = new Label { Text = "Hello MAUI!", FontSize = 24 };
✅ 优势:结构清晰、易于设计工具支持、分离 UI 与逻辑
2.2 核心控件速览
| 控件 | 用途 | 常用属性 |
|---|---|---|
Label |
文本显示 | Text, FontSize, TextColor |
Button |
按钮 | Text, Clicked, BackgroundColor |
Entry |
单行输入 | Text, Placeholder, Keyboard |
Editor |
多行输入 | Text, AutoSize |
Image |
图片 | Source, Aspect |
CheckBox |
复选框 | IsChecked |
Switch |
开关 | IsToggled |
DatePicker / TimePicker |
日期/时间选择 | Date, Time |
📌 所有控件均继承自
View,支持HorizontalOptions、VerticalOptions、Margin、Padding等布局属性。
2.3 布局容器(Layouts)
布局是 UI 的骨架。MAUI 提供多种容器:
(1)VerticalStackLayout / HorizontalStackLayout
-
特点:线性排列子元素
-
适用场景:表单、按钮组、简单列表
<VerticalStackLayout Spacing="10" Padding="20"> <Label Text="用户名" /> <Entry Placeholder="请输入..." /> <Button Text="登录" /> </VerticalStackLayout>
(2)Grid
-
特点:行列网格,支持跨行/跨列
-
适用场景:复杂布局(登录页、仪表盘)
<Grid ColumnDefinitions="*,2*" RowDefinitions="Auto,Auto"> <Label Text="姓名" Grid.Row="0" Grid.Column="0" /> <Entry Grid.Row="0" Grid.Column="1" /> <Button Text="提交" Grid.Row="1" Grid.ColumnSpan="2" /> </Grid>
(3)FlexLayout
- 特点:类似 CSS Flexbox,弹性布局
- 适用场景:响应式流式布局
(4)ScrollView
- 特点:内容超出屏幕时可滚动
- 注意 :不要嵌套
ScrollView,避免性能问题
✅ 最佳实践:
- 优先使用
Grid+StackLayout组合- 避免超过 3 层嵌套
- 使用
Spacing和Padding替代硬编码Margin
第三章:导航系统深度解析(Shell vs 传统)
这是 .NET MAUI 最易混淆的部分。Xamarin.Forms 中的 NavigationPage 已被移除 ,取而代之的是 Shell 导航模型。
3.1 Shell:现代导航的首选
Shell 是一个 声明式导航容器,支持:
- 页面路由(URI-based)
- 底部标签栏(TabBar)
- 侧边栏(Flyout)
- 自动返回栈管理
(1)Shell 基础结构(AppShell.xaml)
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:MyMauiApp.Pages"
x:Class="MyMauiApp.AppShell">
<!-- 单页面模式 -->
<ShellContent ContentTemplate="{DataTemplate pages:MainPage}" />
<!-- 或多标签模式 -->
<!--
<TabBar>
<Tab Title="首页" Icon="home.png">
<ShellContent ContentTemplate="{DataTemplate pages:HomePage}" />
</Tab>
<Tab Title="我的" Icon="profile.png">
<ShellContent ContentTemplate="{DataTemplate pages:ProfilePage}" />
</Tab>
</TabBar>
-->
</Shell>
(2)页面跳转(GoToAsync)
// 注册路由(App.xaml.cs)
Routing.RegisterRoute(nameof(ProfilePage), typeof(ProfilePage));
// 跳转
await Shell.Current.GoToAsync(nameof(ProfilePage));
// 带参数
await Shell.Current.GoToAsync($"{nameof(ProfilePage)}?userId=123");
⚠️ 注意:Shell 每次跳转都会创建新页面实例,这是设计使然。
(3)接收参数(QueryProperty)
[QueryProperty("UserId", "userId")]
public partial class ProfilePage : ContentPage
{
public string UserId { get; set; }
protected override void OnAppearing()
{
base.OnAppearing();
// 使用 UserId 加载数据
}
}
3.2 如何避免重复打开相同页面?
这是高频需求。解决方案如下:
方案一:检查当前路由(推荐)
private async void NavigateToProfile()
{
var currentPath = Shell.Current.CurrentState.Location.OriginalString.Split('?')[0];
if (currentPath == $"/{nameof(ProfilePage)}")
return; // 已在目标页
await Shell.Current.GoToAsync(nameof(ProfilePage));
}
方案二:清空栈后跳转(确保唯一)
await Shell.Current.GoToAsync($"//{nameof(ProfilePage)}");
// "//" 表示从根开始,丢弃当前栈
❌ 不推荐"复用页面实例",因生命周期难以管理。
3.3 传统导航(不推荐,仅作了解)
若未使用 Shell,可手动导航:
await Navigation.PushAsync(new SecondPage());
但需确保 MainPage 被包裹在支持导航的容器中------而 .NET MAUI 已移除 NavigationPage,因此此方式受限。
📌 结论 :始终使用 Shell。
第四章:样式、主题与资源管理
4.1 全局样式(Styles.xaml)
位于 Resources/Styles/Styles.xaml,定义控件默认外观:
<ResourceDictionary>
<Color x:Key="Primary">#512BD4</Color>
<Color x:Key="Secondary">#DFD8F7</Color>
<Style TargetType="Button">
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light={StaticResource Primary}, Dark={StaticResource Secondary}}" />
<Setter Property="TextColor" Value="White" />
<Setter Property="FontSize" Value="16" />
</Style>
</ResourceDictionary>
✅
AppThemeBinding:自动适配系统亮/暗主题
4.2 主题切换
// 强制设置主题
Application.Current.UserAppTheme = AppTheme.Dark;
// 监听系统主题变化
Application.Current.RequestedThemeChanged += (s, e) =>
{
var currentTheme = e.RequestedTheme;
// 更新 UI
};
4.3 图片资源管理
-
将图片放入
Resources/Images/ -
Build Action 设为
MauiImage -
自动适配各平台分辨率(提供 @2x, @3x 版本)
<Image Source="logo.png" />
💡 支持 SVG(需启用):
<Image Source="icon.svg" />
4.4 字体与图标
-
自定义字体:放入
Resources/Fonts/,Build Action =MauiFont -
使用:
<Label Text="" FontFamily="FontAwesome" />
第五章:数据绑定与 MVVM 模式
5.1 什么是数据绑定?
数据绑定实现 UI 与数据的自动同步,无需手动更新控件。
<Label Text="{Binding UserName}" />
5.2 实现 INotifyPropertyChanged
ViewModel 需实现该接口:
public class MainViewModel : INotifyPropertyChanged
{
private string _userName;
public string UserName
{
get => _userName;
set
{
_userName = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
5.3 绑定上下文(BindingContext)
在页面中设置:
public MainPage()
{
InitializeComponent();
BindingContext = new MainViewModel();
}
5.4 使用 CommunityToolkit.MVVM 简化
安装包:
dotnet add package CommunityToolkit.MVVM
简化 ViewModel:
public partial class MainViewModel : ObservableObject
{
[ObservableProperty]
private string userName;
[RelayCommand]
private void LoadData()
{
UserName = "张三";
}
}
✅ 自动生成属性和命令,减少样板代码
第六章:列表与集合展示(CollectionView)
6.1 为什么不用 ListView?
ListView 已过时,CollectionView 是推荐控件,性能更高、功能更强。
6.2 基础用法
<CollectionView ItemsSource="{Binding Users}">
<CollectionView.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}" />
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
6.3 分组、选择、下拉刷新
<CollectionView
ItemsSource="{Binding GroupedUsers}"
IsGrouped="true"
SelectionMode="Single"
RemainingItemsThreshold="5"
RemainingItemsThresholdReached="OnLoadMore">
<CollectionView.GroupHeaderTemplate>
<DataTemplate>
<Label Text="{Binding GroupName}" FontAttributes="Bold" />
</DataTemplate>
</CollectionView.GroupHeaderTemplate>
</CollectionView>
6.4 性能优化
- 使用
RecycleElement(默认) - 避免在 DataTemplate 中嵌套复杂布局
- 虚拟化自动启用
第七章:平台特定功能(Platform-Specific)
7.1 场景举例
- Android:振动、权限请求
- iOS:FaceID、推送通知
- Windows:任务栏集成
7.2 实现方式一:Partial Class
// Shared/VibrationService.cs
public partial class VibrationService
{
public partial void Vibrate(int milliseconds);
}
// Platforms/Android/VibrationService.android.cs
public partial class VibrationService
{
public partial void Vibrate(int milliseconds)
{
var vibrator = (Vibrator)Android.App.Application.Context
.GetSystemService(Android.Content.Context.VibratorService);
vibrator?.Vibrate(milliseconds);
}
}
7.3 实现方式二:依赖注入(推荐)
// 定义接口
public interface IVibrationService
{
void Vibrate(int ms);
}
// Android 实现
public class AndroidVibrationService : IVibrationService
{
public void Vibrate(int ms) { /* ... */ }
}
// 注册(MauiProgram.cs)
builder.Services.AddSingleton<IVibrationService, AndroidVibrationService>();
// 使用
var service = MauiApp.Current.Services.GetService<IVibrationService>();
service.Vibrate(200);
✅ 优势:解耦、可测试、支持 Mock
第八章:生命周期与事件处理
8.1 页面生命周期
| 方法 | 触发时机 | 典型用途 |
|---|---|---|
| 构造函数 | 页面首次创建 | 初始化 ViewModel |
OnAppearing() |
页面即将显示 | 加载数据、启动计时器 |
OnDisappearing() |
页面即将隐藏 | 保存状态、停止计时器 |
⚠️ 常见错误 :在构造函数中加载网络数据 → 应放在
OnAppearing
8.2 应用生命周期(App.xaml.cs)
protected override void OnStart() { } // 应用启动
protected override void OnSleep() { } // 进入后台
protected override void OnResume() { } // 回到前台
💡 可用于保存草稿、释放资源、刷新 token
8.3 事件 vs 命令(Command)
- 事件 :
Clicked="OnButtonClicked"(适合简单逻辑) - 命令 :
Command="{Binding LoadDataCommand}"(适合 MVVM)
✅ 推荐:复杂逻辑用 Command,保持 View 与逻辑分离
第九章:网络请求与本地存储
9.1 HTTP 请求
使用 HttpClient(.NET 内置):
public async Task<User> GetUserAsync(int id)
{
using var client = new HttpClient();
var json = await client.GetStringAsync($"https://api.example.com/users/{id}");
return JsonSerializer.Deserialize<User>(json);
}
💡 建议封装为服务,并注册到 DI 容器
9.2 本地存储
(1)轻量存储:Preferences
Preferences.Set("username", "zhangsan");
var name = Preferences.Get("username", "");
(2)结构化存储:SQLite
安装 sqlite-net-pcl:
public class User
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
}
var db = new SQLiteConnection(dbPath);
db.CreateTable<User>();
db.Insert(new User { Name = "张三" });
第十章:调试、测试与发布
10.1 调试技巧
- 热重载(Hot Reload):修改 XAML/C# 后自动更新(VS 2022 支持)
- 输出日志 :
Debug.WriteLine("...") - 真机调试:比模拟器更真实(尤其涉及传感器)
10.2 单元测试
- 创建
.NET MAUI Class Library项目 - 使用 xUnit/NUnit 测试 ViewModel 和服务
- 避免测试 UI(可用 UI Test 框架,但复杂)
10.3 发布准备
(1)图标与启动屏
- 放入
Resources/AppIcon/和Resources/Splash/ - MAUI 自动处理多分辨率
(2)权限声明(Android)
<!-- Platforms/Android/AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
(3)代码裁剪(Trimming)
- 发布时启用 IL Trimming,减小 APK/IPA 体积
- 注意:反射代码需额外配置
第十一章:性能优化与最佳实践
11.1 布局优化
- 避免深层嵌套(>3 层)
- 使用
Grid替代多个StackLayout - 设置
CachingStrategy="RecycleElement"(CollectionView 默认已启用)
11.2 内存管理
- 及时取消事件订阅(防止内存泄漏)
- 使用
WeakReference处理回调 - 图片使用
Aspect="AspectFit"避免拉伸
11.3 启动速度
- 延迟加载非关键资源
- 减少
MauiProgram.cs中的初始化逻辑 - 使用
SplashScreen提升感知速度
第十二章:常见问题与避坑指南
| 问题 | 解决方案 |
|---|---|
| "XAML 修改不生效" | 清理 + 重启调试;检查拼写;确认 Build Action |
| "图片不显示" | 放入 Resources/Images/;Build Action = MauiImage |
| "找不到 NavigationPage" | 改用 Shell 导航 |
| "多次点击打开多个页面" | 检查当前路由或使用 //Page |
| "Android 权限被拒绝" | 动态请求权限(Android 6.0+) |
| "iOS 构建失败" | 确保 Mac 配对成功;检查 Bundle ID |
结语:.NET MAUI 的未来与学习建议
.NET MAUI 虽仍处于成长期,但已具备生产级能力。随着 .NET 9 的到来(预计 2024 Q4),其性能、工具链和社区生态将进一步完善。
学习路径建议:
- 先掌握 Shell + XAML 布局
- 实践 MVVM + 数据绑定
- 集成网络 + 本地存储
- 尝试平台特定功能
- 发布一个简单 App 到商店
🌈 记住 :跨平台开发的核心不是"一套代码跑 everywhere",而是"一套逻辑,多端体验"。尊重各平台交互习惯,才是专业之道。
附录:推荐资源
- 官方文档 :https://learn.microsoft.com/zh-cn/dotnet/maui/
- 示例代码 :https://github.com/dotnet/maui-samples
- 社区论坛 :https://forums.dotnetfoundation.org/c/maui/
- 免费课程 :Microsoft Learn - .NET MAUI 学习路径
✍️ 本文首发于 CSDN,转载请注明出处 。
如有疑问或需要完整示例项目,欢迎留言交流!