.NET MAUI 跨平台开发全栈指南:从零构建现代化多端应用

📌 前言:为什么选择 .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,支持 HorizontalOptionsVerticalOptionsMarginPadding 等布局属性。

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 层嵌套
  • 使用 SpacingPadding 替代硬编码 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),其性能、工具链和社区生态将进一步完善。

学习路径建议:

  1. 先掌握 Shell + XAML 布局
  2. 实践 MVVM + 数据绑定
  3. 集成网络 + 本地存储
  4. 尝试平台特定功能
  5. 发布一个简单 App 到商店

🌈 记住 :跨平台开发的核心不是"一套代码跑 everywhere",而是"一套逻辑,多端体验"。尊重各平台交互习惯,才是专业之道。


附录:推荐资源


✍️ 本文首发于 CSDN,转载请注明出处

如有疑问或需要完整示例项目,欢迎留言交流!

相关推荐
程序猿小玉兒6 小时前
解决大文件上传失败问题
c#·.net
GfhyPpNY7 小时前
信号交叉口联网燃料电池混合动力汽车生态驾驶的双层凸优化探索
.net
时光追逐者1 天前
使用 NanUI 快速创建具有现代用户界面的 WinForm 应用程序
ui·c#·.net·winform
缺点内向1 天前
在 C# 中为 Word 段落添加制表位:使用 Spire.Doc for .NET 实现高效排版
开发语言·c#·自动化·word·.net
Eiceblue1 天前
通过 C# 解析 HTML:文本提取 + 结构化数据获取
c#·html·.net·visual studio
一叶星殇1 天前
.NET 6 NLog 实现多日志文件按业务模块拆分的实践
开发语言·.net
时光追逐者1 天前
一款基于 .NET Avalonia 开源免费、快速、跨平台的图片查看器
c#·.net·图片查看器
酩酊仙人1 天前
.Net机器学习入门
人工智能·机器学习·.net
CodeCraft Studio1 天前
如何借助TeeChart图表库,实现放射治疗QA数据的精准可视化
信息可视化·.net·数据可视化·teechart·医疗软件·医疗数据分析·医用图表