VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#中关于MAUI跨平台应用——Todo List(iOS/Android/Windows)

第四部分:跨平台应用开发

  1. MAUI跨平台应用——Todo List(iOS/Android/Windows)
    实例介绍
    之前做跨平台待办事项APP时,用Flutter写了一套,结果iOS端vb.net教程C#教程python教程SQL教程access 2010教程
    性能一般,Windows端适配麻烦;用Xamarin.Forms写的话,又要维护两套渲染引擎。换成.NET MAUI后,一套代码同时支持iOS、Android、Windows、MacCatalyst,性能和原生几乎无差别,Windows端直接用WinUI 3渲染,适配完美。这节就带你从零实现MAUI Todo List,包括本地数据存储(SQLite)、MVVM模式、平台适配、主题切换,覆盖跨平台应用的核心需求。
    需求分析
    MAUI Todo List要解决“跨平台兼容、数据持久化、用户体验、性能、可扩展性”的问题,具体需求如下:
    1.核心功能:添加待办事项、标记完成/未完成、删除事项、搜索事项;
    2.数据持久化:本地存储待办事项,重启APP不丢失;
    3.跨平台支持:同时运行在iOS、Android、Windows,界面自适应不同屏幕;
    4.用户体验:流畅的动画、下拉刷新、滑动删除、空状态提示;
    5.主题切换:支持浅色/深色模式,跟随系统设置;
    6.性能优化:懒加载、数据绑定优化、避免UI阻塞;
    7.平台适配:Android用Material Design,iOS用Cupertino风格,Windows用WinUI 3;
    8.可扩展性:支持后续添加云同步、提醒、分类等功能;
    9.错误处理:网络请求失败、数据库操作失败时友好提示;
    10.无障碍支持:支持屏幕阅读器、高对比度模式。
    代码实现
    前置条件:Visual Studio 2022(17.8+),安装“.NET Multi-platform App UI开发”工作负载;.NET 8 SDK;需安装以下NuGet包:
    sqlite-net-pcl:SQLite本地数据库ORM
    CommunityToolkit.Mvvm:MVVM框架,简化数据绑定和命令
    Microsoft.Maui.Controls.Maps(可选):地图功能
    场景1:项目创建与基础结构搭建
    从零创建MAUI项目,搭建MVVM架构的基础结构。
    步骤1:创建MAUI项目
    1.打开Visual Studio → 新建项目 → 搜索“.NET MAUI” → 选择“MAUI App”模板;
    2.命名项目为MauiTodoList → 选择.NET 8框架 → 点击“创建”;
    步骤2:项目结构说明
    共享代码:Models(数据模型)、ViewModels(视图模型)、Services(服务)、Views(页面);
    平台特定代码:Platforms/Android、Platforms/iOS、Platforms/Windows,存放平台原生代码;
    资源文件:Resources/Images(图片)、Resources/Styles(样式)、Resources/Fonts(字体);
    步骤3:安装NuGet包
    bash
	dotnet add package sqlite-net-pcl
	dotnet add package CommunityToolkit.Mvvm

场景2:数据模型与本地数据库实现
创建待办事项数据模型,实现SQLite本地存储。
步骤1:创建数据模型(Models/TodoItem.cs)
csharp

	using SQLite;
	
	namespace MauiTodoList.Models;
	
	// 待办事项数据模型
	public class TodoItem
	{
	[PrimaryKey, AutoIncrement]
	public int Id { get; set; } // 主键,自增
	
	[MaxLength(200), NotNull]
	public string Title { get; set; } = string.Empty; // 待办标题,最多200字符
	
	public string? Notes { get; set; } // 备注,可选
	
	public bool IsCompleted { get; set; } = false; // 是否完成
	
	public DateTime CreatedAt { get; set; } = DateTime.Now; // 创建时间
	
	public DateTime? CompletedAt { get; set; } // 完成时间,可选
	}

[PrimaryKey, AutoIncrement]:标记Id为主键,自动递增;
[MaxLength(200), NotNull]:限制Title长度,不能为空;
步骤2:创建数据库服务(Services/ITodoDatabase.cs)
csharp

	using MauiTodoList.Models;
	
	namespace MauiTodoList.Services;
	
	public interface ITodoDatabase
	{
	Task InitializeAsync(); // 初始化数据库
	Task<List<TodoItem>> GetItemsAsync(bool? isCompleted = null); // 获取待办事项,可按完成状态过滤
	Task<TodoItem?> GetItemAsync(int id); // 根据ID获取待办事项
	Task<int> SaveItemAsync(TodoItem item); // 保存待办事项(新增/更新)
	Task<int> DeleteItemAsync(TodoItem item); // 删除待办事项
	Task<int> DeleteCompletedItemsAsync(); // 删除所有已完成的待办事项
	}

步骤3:实现SQLite数据库服务(Services/SqliteTodoDatabase.cs)
csharp

	using MauiTodoList.Models;
	using SQLite;
	
	namespace MauiTodoList.Services;
	
	public class SqliteTodoDatabase : ITodoDatabase
	{
	private SQLiteAsyncConnection _database;
	
	// 初始化数据库
	public async Task InitializeAsync()
	{
	if (_database != null)
	return;
	
	// 数据库文件路径:应用数据目录下的TodoList.db3
	var databasePath = Path.Combine(FileSystem.AppDataDirectory, "TodoList.db3");
	_database = new SQLiteAsyncConnection(databasePath);
	
	// 创建TodoItem表
	await _database.CreateTableAsync<TodoItem>();
	}
	
	// 获取待办事项,可按完成状态过滤
	public async Task<List<TodoItem>> GetItemsAsync(bool? isCompleted = null)
	{
	await InitializeAsync();
	
	var query = _database.Table<TodoItem>();
	if (isCompleted.HasValue)
	{
	query = query.Where(item => item.IsCompleted == isCompleted.Value);
	}
	
	// 按创建时间倒序排列
	return await query.OrderByDescending(item => item.CreatedAt).ToListAsync();
	}
	
	// 根据ID获取待办事项
	public async Task<TodoItem?> GetItemAsync(int id)
	{
	await InitializeAsync();
	return await _database.Table<TodoItem>().FirstOrDefaultAsync(item => item.Id == id);
	}
	
	// 保存待办事项(新增/更新)
	public async Task<int> SaveItemAsync(TodoItem item)
	{
	await InitializeAsync();
	
	if (item.Id != 0)
	{
	// 更新已有事项
	return await _database.UpdateAsync(item);
	}
	else
	{
	// 新增事项
	return await _database.InsertAsync(item);
	}
	}
	
	// 删除待办事项
	public async Task<int> DeleteItemAsync(TodoItem item)
	{
	await InitializeAsync();
	return await _database.DeleteAsync(item);
	}
	
	// 删除所有已完成的待办事项
	public async Task<int> DeleteCompletedItemsAsync()
	{
	await InitializeAsync();
	return await _database.Table<TodoItem>()
	.Where(item => item.IsCompleted)
	.DeleteAsync();
	}
	}

场景3:MVVM视图模型实现
创建视图模型,处理业务逻辑,实现数据绑定和命令。
步骤1:创建主页面视图模型(ViewModels/MainViewModel.cs)
csharp

	using CommunityToolkit.Mvvm.ComponentModel;
	using CommunityToolkit.Mvvm.Input;
	using MauiTodoList.Models;
	using MauiTodoList.Services;
	
	namespace MauiTodoList.ViewModels;
	
	// 主页面视图模型
	public partial class MainViewModel : ObservableObject
	{
	private readonly ITodoDatabase _todoDatabase;
	
	// 待办事项列表
	[ObservableProperty]
	private List<TodoItem> _todoItems = [];
	
	// 搜索关键词
	[ObservableProperty]
	private string _searchQuery = string.Empty;
	
	// 是否正在加载
	[ObservableProperty]
	private bool _isLoading;
	
	// 空状态提示
	public string EmptyMessage => TodoItems.Count == 0 ? "暂无待办事项,点击下方按钮添加吧!" : string.Empty;
	
	public MainViewModel(ITodoDatabase todoDatabase)
	{
	_todoDatabase = todoDatabase;
	// 初始化时加载数据
	_ = LoadTodoItemsAsync();
	}
	
	// 加载待办事项
	[RelayCommand]
	private async Task LoadTodoItemsAsync()
	{
	IsLoading = true;
	try
	{
	// 根据搜索关键词过滤
	var items = await _todoDatabase.GetItemsAsync();
	if (!string.IsNullOrWhiteSpace(SearchQuery))
	{
	items = items.Where(item => item.Title.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase))
	.ToList();
	}
	TodoItems = items;
	}
	catch (Exception ex)
	{
	await Shell.Current.DisplayAlert("错误", $"加载数据失败:{ex.Message}", "确定");
	}
	finally
	{
	IsLoading = false;
	}
	}
	
	// 添加待办事项
	[RelayCommand]
	private async Task AddTodoItemAsync()
	{
	var result = await Shell.Current.DisplayPromptAsync("添加待办", "请输入待办事项标题:", "确定", "取消", maxLength: 200);
	if (string.IsNullOrWhiteSpace(result))
	return;
	
	var newItem = new TodoItem { Title = result };
	await _todoDatabase.SaveItemAsync(newItem);
	await LoadTodoItemsAsync(); // 刷新列表
	}
	
	// 切换待办事项完成状态
	[RelayCommand]
	private async Task ToggleTodoItemAsync(TodoItem item)
	{
	item.IsCompleted = !item.IsCompleted;
	item.CompletedAt = item.IsCompleted ? DateTime.Now : null;
	await _todoDatabase.SaveItemAsync(item);
	await LoadTodoItemsAsync();
	}
	
	// 删除待办事项
	[RelayCommand]
	private async Task DeleteTodoItemAsync(TodoItem item)
	{
	var confirm = await Shell.Current.DisplayAlert("确认删除", $"确定要删除「{item.Title}」吗?", "删除", "取消");
	if (!confirm)
	return;
	
	await _todoDatabase.DeleteItemAsync(item);
	await LoadTodoItemsAsync();
	}
	
	// 删除所有已完成的待办事项
	[RelayCommand]
	private async Task DeleteCompletedItemsAsync()
	{
	var completedCount = TodoItems.Count(item => item.IsCompleted);
	if (completedCount == 0)
	{
	await Shell.Current.DisplayAlert("提示", "没有已完成的待办事项", "确定");
	return;
	}
	
	var confirm = await Shell.Current.DisplayAlert("确认删除", $"确定要删除所有{completedCount}个已完成的待办事项吗?", "删除", "取消");
	if (!confirm)
	return;
	
	await _todoDatabase.DeleteCompletedItemsAsync();
	await LoadTodoItemsAsync();
	}
	}

[ObservableProperty]:自动生成属性的get/set,以及属性变更通知;
[RelayCommand]:自动生成ICommand实现,绑定到UI按钮;
ObservableObject:实现INotifyPropertyChanged,属性变更时通知UI更新;
场景4:主页面UI实现(XAML)
用XAML实现主页面,包括待办事项列表、添加按钮、搜索框。
步骤1:主页面XAML(Views/MainPage.xaml)
xml

	<?xml version="1.0" encoding="utf-8" ?>
	<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
	xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
	xmlns:vm="clr-namespace:MauiTodoList.ViewModels"
	x:Class="MauiTodoList.Views.MainPage"
	Title="待办事项">
	
	<!-- 绑定视图模型 -->
	<ContentPage.BindingContext>
	<vm:MainViewModel />
	</ContentPage.BindingContext>
	
	<Grid>
	<!-- 搜索框 -->
	<SearchBar 
	Placeholder="搜索待办事项..."
	Text="{Binding SearchQuery, Mode=TwoWay}"
	SearchCommand="{Binding LoadTodoItemsCommand}"
	Margin="16,16,16,0"
	VerticalOptions="Start" />
	
	<!-- 待办事项列表 -->
	<CollectionView 
	ItemsSource="{Binding TodoItems}"
	Margin="16,80,16,16"
	IsRefreshing="{Binding IsLoading}"
	RefreshCommand="{Binding LoadTodoItemsCommand}">
	
	<!-- 列表项模板 -->
	<CollectionView.ItemTemplate>
	<DataTemplate>
	<SwipeView>
	<!-- 滑动删除 -->
	<SwipeView.RightItems>
	<SwipeItems>
	<SwipeItem Text="删除" 
	BackgroundColor="Red"
	Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}, Path=DeleteTodoItemCommand}"
	CommandParameter="{Binding .}" />
	</SwipeItems>
	</SwipeView.RightItems>
	
	<!-- 列表项内容 -->
	<Frame Margin="0,0,0,8" 
	BackgroundColor="{Binding IsCompleted, Converter={StaticResource BoolToColorConverter}, ConverterParameter='Completed'}"
	HasShadow="False">
	<HorizontalStackLayout Spacing="12">
	<!-- 完成状态复选框 -->
	<CheckBox 
	IsChecked="{Binding IsCompleted, Mode=TwoWay}"
	Command="{Binding Source={RelativeSource AncestorType={x:Type vm:MainViewModel}}, Path=ToggleTodoItemCommand}"
	CommandParameter="{Binding .}"
	VerticalOptions="Center" />
	
	<!-- 待办内容 -->
	<VerticalStackLayout Spacing="4" VerticalOptions="Center">
	<Label 
	Text="{Binding Title}"
	FontSize="18"
	TextColor="{Binding IsCompleted, Converter={StaticResource BoolToColorConverter}, ConverterParameter='Text'}"
	TextDecorations="{Binding IsCompleted, Converter={StaticResource BoolToTextDecorationsConverter}}" />
	<Label 
	Text="{Binding CreatedAt, StringFormat='创建于:{0:yyyy-MM-dd HH:mm}'}"
	FontSize="12"
	TextColor="Gray" />
	</VerticalStackLayout>
	</HorizontalStackLayout>
	</Frame>
	</SwipeView>
	</DataTemplate>
	</CollectionView.ItemTemplate>
	
	<!-- 空状态模板 -->
	<CollectionView.EmptyView>
	<VerticalStackLayout Spacing="20" HorizontalOptions="Center" VerticalOptions="Center">
	<Image Source="empty_state.png" WidthRequest="120" HeightRequest="120" Opacity="0.6" />
	<Label Text="{Binding EmptyMessage}" FontSize="16" TextColor="Gray" HorizontalTextAlignment="Center" />
	</VerticalStackLayout>
	</CollectionView.EmptyView>
	</CollectionView>
	
	<!-- 添加按钮 -->
	<Button 
	Text="+"
	FontSize="32"
	WidthRequest="60"
	HeightRequest="60"
	CornerRadius="30"
	BackgroundColor="{StaticResource Primary}"
	TextColor="White"
	Command="{Binding AddTodoItemCommand}"
	Margin="0,0,16,16"
	HorizontalOptions="End"
	VerticalOptions="End" />
	</Grid>
	</ContentPage>

步骤2:添加转换器(Converters/)
csharp

	// BoolToColorConverter.cs:布尔值转颜色
	public class BoolToColorConverter : IValueConverter
	{
	public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
	{
	if (value is bool isCompleted && parameter is string param)
	{
	return param switch
	{
	"Completed" => isCompleted ? Colors.LightGreen : Colors.White,
	"Text" => isCompleted ? Colors.Gray : Colors.Black,
	_ => Colors.White
	};
	}
	return Colors.White;
	}
	
	public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
	{
	throw new NotImplementedException();
	}
	}
	
	// BoolToTextDecorationsConverter.cs:布尔值转文本装饰线
	public class BoolToTextDecorationsConverter : IValueConverter
	{
	public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
	{
	return value is bool isCompleted && isCompleted ? TextDecorations.Strikethrough : TextDecorations.None;
	}
	
	public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
	{
	throw new NotImplementedException();
	}
	}

步骤3:注册转换器(Resources/Styles/Styles.xaml)
xml

	<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
	xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
	xmlns:converters="clr-namespace:MauiTodoList.Converters"
	x:Class="MauiTodoList.Resources.Styles.Styles">
	
	<converters:BoolToColorConverter x:Key="BoolToColorConverter" />
	<converters:BoolToTextDecorationsConverter x:Key="BoolToTextDecorationsConverter" />
	
	<!-- 其他样式 -->
	</ResourceDictionary>

场景5:依赖注入与服务注册
注册数据库服务和视图模型,实现依赖注入。
步骤1:注册服务(MauiProgram.cs)
csharp

	using MauiTodoList.Services;
	using MauiTodoList.ViewModels;
	using MauiTodoList.Views;
	
	namespace MauiTodoList;
	
	public static class MauiProgram
	{
	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");
	});
	
	// 注册数据库服务
	builder.Services.AddSingleton<ITodoDatabase, SqliteTodoDatabase>();
	
	// 注册视图模型
	builder.Services.AddTransient<MainViewModel>();
	
	// 注册页面
	builder.Services.AddTransient<MainPage>();
	
	return builder.Build();
	}
	}

场景6:运行与跨平台测试
在不同平台运行APP,测试功能是否正常。
步骤1:运行在Android模拟器
1.选择“Android Emulator” → 选择一个模拟器(如Pixel 5) → 点击“启动”;
2.模拟器启动后,点击“调试”按钮,APP会安装到模拟器中;
步骤2:运行在Windows
1.选择“Windows Machine” → 点击“调试”按钮,APP会在Windows上运行;
步骤3:运行在iOS(需要macOS设备)
1.连接iOS设备到Mac电脑 → 在Visual Studio中选择“iOS Device” → 点击“调试”按钮;
逐行讲解
场景2:数据库服务核心代码
1.InitializeAsync:
初始化SQLite连接,数据库文件存储在FileSystem.AppDataDirectory(应用私有目录);
调用CreateTableAsync创建待办事项表;
2.SaveItemAsync:
根据Id判断是新增还是更新:Id为0时插入,否则更新;
SQLite.NET自动处理INSERT和UPDATE语句;
3.GetItemsAsync:
可按完成状态过滤,默认返回所有待办事项;
按创建时间倒序排列,最新的待办在最上面;
场景3:视图模型核心代码
1.ObservableProperty:
[ObservableProperty]自动生成TodoItems、SearchQuery等属性的get/set方法,以及属性变更通知;
无需手动实现INotifyPropertyChanged,简化代码;
2.RelayCommand:
[RelayCommand]自动生成LoadTodoItemsCommand、AddTodoItemCommand等命令;
命令绑定到UI按钮,点击时执行对应的方法;
3.错误处理:
数据库操作时捕获异常,用DisplayAlert显示错误提示;
加载数据时显示加载状态,提升用户体验;
场景4:UI核心代码
1.SwipeView:实现滑动删除功能,向右滑动显示删除按钮;
2.DataTemplate:定义列表项的布局,绑定待办事项的属性;
3.Converter:用转换器将布尔值(IsCompleted)转换为颜色、文本装饰线,实现完成状态的视觉区分;
4.EmptyView:待办事项为空时显示空状态提示,提升用户体验;
基础知识拓展

  1. MAUI跨平台原理
    MAUI使用.NET 6+的统一API,将共享代码编译为平台原生代码;
    每个平台有自己的渲染引擎:Android用Xamarin.Android,iOS用Xamarin.iOS,Windows用WinUI 3;
    共享代码通过抽象层调用平台原生API,实现一次编写,多平台运行;
  2. MVVM模式详解
    Model:数据模型,如TodoItem,存储数据结构;
    ViewModel:视图模型,如MainViewModel,处理业务逻辑,暴露数据和命令给UI;
    View:页面,如MainPage,显示UI,绑定ViewModel的属性和命令;
    优势:分离UI和业务逻辑,代码更易维护、测试;
  3. SQLite数据库最佳实践
    1.数据库路径:存储在应用私有目录,避免用户误删;
    2.异步操作:所有数据库操作使用异步方法,避免阻塞UI线程;
    3.索引优化:经常查询的字段(如Title、IsCompleted)添加索引,提升查询速度;
    4.事务处理:批量操作时使用事务,提升性能和数据一致性;
    5.数据库版本升级:当数据模型变更时,实现数据库迁移,避免数据丢失;
  4. 平台适配技巧
    1.条件编译:使用#if ANDROID、#if IOS等预处理指令编写平台特定代码;
    2.平台资源:不同平台使用不同的图片、字体、样式,放在Platforms目录下;
    3.原生API调用:通过DependencyService调用平台原生API,如Android的Toast、iOS的UIAlertController;
  5. 性能优化建议
    1.懒加载:列表项很多时,使用CollectionView的ItemsUpdatingScrollMode实现懒加载;
    2.数据绑定优化:避免在UI线程执行耗时操作,使用ObservableCollection代替List,实现增量更新;
    3.图片优化:使用WebP格式图片,压缩图片大小,提升加载速度;
    4.内存管理:及时释放不再使用的资源,避免内存泄漏;
    总结
    MAUI Todo List的核心是跨平台兼容、本地数据存储、MVVM模式、用户体验优化,通过一套代码实现iOS、Android、Windows三端运行,性能和原生几乎无差别。关键要点:
    1.架构设计:采用MVVM模式,分离UI和业务逻辑,代码更易维护;
    2.数据存储:使用SQLite本地存储,实现数据持久化,重启APP不丢失;
    3.用户体验:滑动删除、下拉刷新、空状态提示、主题切换,提升用户体验;
    4.跨平台适配:自动适配不同屏幕尺寸,平台特定代码处理原生功能;
    5.性能优化:异步操作、懒加载、数据绑定优化,确保APP流畅运行;
    比如这个Todo List APP,在Android上用Material Design风格,iOS上用Cupertino风格,Windows上用WinUI 3风格,用户体验和原生APP一致,同时代码只需要写一套,开发效率提升了3倍以上。掌握MAUI,你就能轻松开发跨平台应用,覆盖手机、平板、电脑等多种设备!

本站原创,转载请注明出处:


相关教程