从零到发布用.NET MAUI构建一个多平台待办事项应用含iOS/Android/Windows适配技巧在移动互联网时代用户期望能在不同设备间无缝切换使用同一款应用。作为开发者我们常常面临一个难题如何高效地为iOS、Android和Windows等多个平台开发功能一致的应用传统方案需要维护多套代码不仅开发周期长后期更新维护更是噩梦。而.NET MAUI的出现彻底改变了这一局面。.NET MAUI.NET Multi-platform App UI是微软推出的跨平台UI框架它允许开发者使用C#和XAML编写一次代码就能生成适配多个平台的原生应用。不同于早期的Xamarin.FormsMAUI深度集成在.NET 6生态中性能更优开发体验更流畅。本文将带你从零开始构建一个功能完整的待办事项应用并分享我在实际项目中总结的平台适配技巧。1. 开发环境搭建与项目初始化1.1 必备工具链配置开始前需要准备以下开发环境Visual Studio 2022 17.3社区版即可满足需求.NET 7 SDK推荐或.NET 6 LTS版本平台特定工具Android开发Android SDK 31iOS开发需macOS设备Xcode 14Windows开发Windows 10/11 SDK安装时务必勾选以下工作负载使用.NET的移动开发使用.NET的桌面开发提示如果遇到Android模拟器启动问题可尝试通过Android Device Manager创建ARM64架构的模拟器性能比x86更优。1.2 创建MAUI项目在VS中新建项目时选择MAUI App模板。建议采用以下项目结构TodoApp/ ├── Platforms/ # 各平台特定代码 ├── Resources/ # 共享资源 ├── Services/ # 数据服务层 ├── ViewModels/ # MVVM视图模型 ├── Views/ # 页面视图 └── Models/ # 数据模型初始化后首先修改App.xaml.cs设置启动页public partial class App : Application { public App() { InitializeComponent(); MainPage new NavigationPage(new MainView()); } }2. 核心功能实现2.1 数据模型设计待办事项应用的核心是任务管理我们先定义基础模型public class TodoItem : ObservableObject { private string _title; public string Title { get _title; set SetProperty(ref _title, value); } private bool _isCompleted; public bool IsCompleted { get _isCompleted; set SetProperty(ref _isCompleted, value); } public DateTime CreatedAt { get; } DateTime.Now; }使用ObservableObject实现属性变更通知这是MVVM模式的关键。接着创建服务层public interface ITodoService { Task AddItemAsync(TodoItem item); Task RemoveItemAsync(TodoItem item); TaskIEnumerableTodoItem GetItemsAsync(); } // 使用SQLite实现本地存储 public class TodoService : ITodoService { private readonly SQLiteAsyncConnection _database; public TodoService() { _database new SQLiteAsyncConnection( Path.Combine(FileSystem.AppDataDirectory, todos.db3)); _database.CreateTableAsyncTodoItem(); } public async Task AddItemAsync(TodoItem item) await _database.InsertAsync(item); // 其他方法实现... }2.2 界面开发实战主页面采用CollectionView展示任务列表配合DataTemplate定义项模板ContentPage xmlnshttp://schemas.microsoft.com/dotnet/2022/maui xmlns:viewModelsclr-namespace:TodoApp.ViewModels x:ClassTodoApp.Views.MainView Title我的待办 Grid RowDefinitions*,Auto CollectionView ItemsSource{Binding Items} SelectionModeNone CollectionView.ItemTemplate DataTemplate SwipeView SwipeView.RightItems SwipeItem Text删除 Command{Binding Source{RelativeSource AncestorType{x:Type viewModels:MainViewModel}}, PathDeleteCommand} CommandParameter{Binding .} BackgroundColorRed/ /SwipeView.RightItems Grid Padding10 CheckBox IsChecked{Binding IsCompleted} VerticalOptionsCenter/ Label Text{Binding Title} FontSize16 Margin30,0,0,0/ /Grid /SwipeView /DataTemplate /CollectionView.ItemTemplate /CollectionView Entry Grid.Row1 Placeholder新增任务... ReturnCommand{Binding AddCommand} ReturnCommandParameter{Binding Text, Source{x:Reference entry}} x:Nameentry/ /Grid /ContentPage视图模型处理业务逻辑public class MainViewModel : ObservableObject { private readonly ITodoService _todoService; public ObservableCollectionTodoItem Items { get; } new(); public ICommand AddCommand { get; } public ICommand DeleteCommand { get; } public MainViewModel(ITodoService todoService) { _todoService todoService; AddCommand new Commandstring(async title { if(string.IsNullOrWhiteSpace(title)) return; var item new TodoItem { Title title }; await _todoService.AddItemAsync(item); Items.Add(item); }); DeleteCommand new CommandTodoItem(async item { await _todoService.RemoveItemAsync(item); Items.Remove(item); }); LoadData(); } private async void LoadData() { var items await _todoService.GetItemsAsync(); Items.Clear(); foreach(var item in items) Items.Add(item); } }3. 多平台适配技巧3.1 平台特定UI调整不同平台有各自的UI规范可通过OnPlatform实现差异化Button Text保存 Button.FontSize OnPlatform x:TypeArgumentsx:Double On PlatformiOS Value16/ On PlatformAndroid Value14/ On PlatformWinUI Value12/ /OnPlatform /Button.FontSize /Button对于复杂差异可使用平台特定代码// 在Platforms/Android/文件夹下 public static class PlatformStyles { public static void ApplyAndroidSpecificStyles() { Microsoft.Maui.Handlers.EntryHandler.Mapper.AppendToMapping( NoUnderline, (h, _) { h.PlatformView.BackgroundTintList Android.Content.Res.ColorStateList.ValueOf(Colors.Transparent.ToPlatform()); }); } }3.2 功能特性适配不同平台的能力差异需要特殊处理功能需求Android实现方案iOS实现方案Windows实现方案后台任务WorkManagerBackgroundTasksBackgroundTask本地通知NotificationCompatUNUserNotificationCenterToastNotification文件存储MediaStoreFileManagerStorageFile例如实现通知功能public interface INotificationService { void ScheduleReminder(TodoItem item); } // Android实现 public class AndroidNotificationService : INotificationService { public void ScheduleReminder(TodoItem item) { var context Android.App.Application.Context; var intent new Android.Content.Intent(context, typeof(MainActivity)); var pendingIntent PendingIntent.GetActivity( context, 0, intent, PendingIntentFlags.Immutable); var builder new NotificationCompat.Builder(context, todo_channel) .SetContentTitle(待办提醒) .SetContentText(item.Title) .SetSmallIcon(Resource.Drawable.notification_icon) .SetContentIntent(pendingIntent); var manager NotificationManagerCompat.From(context); manager.Notify(item.GetHashCode(), builder.Build()); } }3.3 性能优化要点跨平台应用需要特别注意性能问题图片资源处理使用FontImageSource替代小图标大图采用Downsample降低分辨率列表优化设置CollectionView.CachingStrategyRecycleElement避免复杂的项模板层级平台渲染差异Android上启用硬件加速iOS避免透明视图重叠Image Sourcelarge_bg.jpg Image.HeightRequest OnPlatform x:TypeArgumentsx:Double On PlatformiOS Value200/ On PlatformAndroid Value180/ /OnPlatform /Image.HeightRequest /Image4. 测试与发布准备4.1 多平台调试策略VS提供多种调试方式热重载修改XAML或C#代码即时生效设备管理器同时连接多个设备对比测试平台模拟使用Android模拟器不同API级别建议测试矩阵测试项AndroidiOSWindows基础CRUD操作✔✔✔横竖屏切换✔✔✔深色模式✔✔✔离线状态✔✔✔4.2 应用发布指南各平台发布流程对比Android发布生成签名APK或AAB创建Google Play开发者账号准备应用截图和宣传文案iOS发布需Apple开发者账号年费$99创建App ID和配置文件通过App Store Connect提交审核Windows发布打包为MSIX或APPX提交到Microsoft Store可选旁加载方式分发发布前关键检查项应用图标适配所有分辨率隐私政策链接配置权限声明准确无误多语言本地化支持# 发布编译命令示例 dotnet publish -f net7.0-android -c Release dotnet publish -f net7.0-ios -c Release5. 进阶功能扩展在实际项目中我们往往需要扩展更多企业级功能。以下是我在多个MAUI项目中验证过的可靠方案5.1 云端同步实现结合Azure Mobile Apps实现跨设备同步public class CloudSyncService { private readonly IMobileServiceClient _client; public CloudSyncService() { _client new MobileServiceClient(https://your-app.azurewebsites.net); } public async Task SyncAsync() { var localItems await _todoService.GetItemsAsync(); var remoteTable _client.GetTableTodoItem(); try { var remoteItems await remoteTable.ToListAsync(); // 实现差异合并逻辑... } catch(Exception ex) { Debug.WriteLine($Sync failed: {ex.Message}); } } }5.2 自动化构建配置通过Directory.Build.props统一管理依赖版本Project PropertyGroup TargetFrameworknet7.0/TargetFramework SupportedOSPlatformVersion Condition$([MSBuild]::GetTargetPlatformIdentifier($(TargetFramework))) ios14.2/SupportedOSPlatformVersion SupportedOSPlatformVersion Condition$([MSBuild]::GetTargetPlatformIdentifier($(TargetFramework))) android21.0/SupportedOSPlatformVersion /PropertyGroup ItemGroup PackageReference IncludeSQLiteNetExtensions Version2.1.0/ PackageReference IncludeAzure.Data.Tables Version12.0.0/ /ItemGroup /Project5.3 异常监控集成接入AppCenter收集崩溃日志public static MauiApp CreateMauiApp() { var builder MauiApp.CreateBuilder(); builder .UseMauiAppApp() .ConfigureLifecycleEvents(events { events.AddAndroid(android { android.OnCreate((activity, _) Microsoft.AppCenter.AppCenter.Start( android{Your-Android-App-Secret}, typeof(Microsoft.AppCenter.Analytics.Analytics), typeof(Microsoft.AppCenter.Crashes.Crashes))); }); // 其他平台配置... }); return builder.Build(); }在项目开发过程中我发现MAUI的Shell导航特别适合复杂应用结构。通过定义路由体系可以优雅地处理深层链接// 注册路由 Routing.RegisterRoute(details, typeof(DetailPage)); // 导航使用 await Shell.Current.GoToAsync(details?itemId123); // 接收参数 protected override void OnAppearing() { base.OnAppearing(); var itemId (Shell.Current.CurrentState.Location.OriginalString .Split()[1]); // 加载数据... }