乘风破浪,.Net Core遇见MAUI(.NET Multi-platform App UI),进击现代化跨设备应用框架

2023-05-22,,

什么是MAUI

https://github.com/dotnet/maui

.NET Multi-platform App UI (MAUI) 的前身是Xamarin.Forms(适用于Android,iOS和UWP的跨平台移动优先框架),.NET MAUI是Xamarin.Forms的演进。我们拥有7年的为客户提供技术支持的经验,服务对象从独立开发人员到一些全球性的大公司,我们正在改善产品的核心功能,加快UI渲染,投资研发一致的系统设计模式,并从移动端扩展到桌面端。

.NET 6 deep dive; what's new and what's coming

与WinUI的区别

MAUI是一个跨iOS、Android、Windows、MacOs多设备多生态的应用框架,而WinUI仅限于Windows。

面向用户

.NET MAUI is for developers who want to:

Write cross-platform apps in XAML and C#, from a single shared code-base in Visual Studio.
Share UI layout and design across platforms.
Share code, test, and business logic across platforms.

工作原理

.NET MAUI将Android、iOS、macOS和WindowsAPI统一为单个API,允许在任何地方开发人员处进行笔写体验,同时提供对每个本地平台各个方面的深度访问。

.NET 6为创建应用提供了一系列特定于平台的框架:.Android的.NET、iOS的.NET、macOS的.NET和WindowsUI(WinUI)库。这些框架都可以访问相同的.NET 6基础类库(BCL)。此库将基础平台的详细信息从您的代码中摘要出来。BCL取决于.NET运行时间为您的代码提供执行环境。对于安卓、iOS和macOS,环境由单声道实施,这是.NET运行时间的实现。在Windows上,WinRT执行相同的角色,但窗口平台已优化除外。

虽然BCL使在不同平台上运行的应用能够共享共同的商业逻辑,但不同的平台具有定义应用用户界面的不同方式,它们为指定用户界面元素的沟通和互操作方式提供了不同的模型。您可以使用适当的平台特定框架(.Android的NET、iOS的.NET、macOS的.NET或WinUI)分别为每个平台制作UI,但此方法需要您为每个设备系列维护代码基础。

.NET MAUI为为移动和桌面应用构建UI提供了单一框架。下图显示了.NETMAUI应用程序的架构的高层视图:

在.NET MAUI应用中,您编写的代码主要与NET MAUI API交互。.NET MAUI然后直接消耗原生平台API。此外,如果需要,应用代码可以直接执行平台API。

.NET MAUI应用可以写在PC或Mac上,并编译为原生应用包:

使用.NET MAUI构建的Android应用从C#编译为中间语言(IL),然后在应用启动时及时(JIT)编译到本地装配中。
使用.NET MAUI构建的iOS应用完全提前从C#编译为原生ARM装配代码。
使用.NET MAUI构建的macOS应用使用MacCatalyst,这是苹果的解决方案,将使用UIKit构建的iOS应用引入桌面,并根据需要通过额外的AppKit和平台API来增强它。
使用.NET MAUI构建的Windows应用使用WindowsUI库(WinUI)3创建可以针对Windows桌面和通用视窗平台(UWP)的原生应用。

.NET MAUI单项目

.NET MAUI应用通常由一个可以针对安卓、iOS、macOS和Windows的项目组成。这提供了以下好处:

一个针对多个平台和设备的项目。
一个位置来管理资源,如字体和图像。
多目标组织平台特定代码。

支持平台

.NET多平台应用UI(MAUI)应用可用于以下平台编写:

Android 5.0 (API 21) or higher.
iOS 10 or higher.
macOS 11 (Big Sur) or higher.
Windows desktop and the Universal Windows Platform (UWP), using Windows UI Library (WinUI) 3.

适用于安卓、iOS和视窗的NET MAUI应用程序可在视觉工作室内置。但是,使用最新版本的Xcode和Apple所需的macOS的最小版本进行iOS开发需要联网Mac。

适用于安卓、iOS和macOS的NET茂宜岛应用程序可在Mac视觉工作室内置。

前置条件

创建MAUI的前置条件是.Net的版本必须大于等于.NET 6 Preview 5

而且Visual Studio版本必须大于等于Visual Studio 16.11 Preview 2

如果要创建MAUI For Windows App,还需要额外安装两个扩展:

Project Reunion (Preview)
Single-project MSIX Packaging Tools

安装.NET 6 Preview

我们可以使用官方提供的一个工具来检查我们需要运行MAUI的环境是否齐全,还是很贴心哈。

https://github.com/redth/dotnet-maui-check

安装MAUI环境检查工具(Maui.Check)

dotnet tool install -g Redth.Net.Maui.Check

运行MAUI环境检查工具(Maui.Check)

maui-check

然后会弹出这个检查工具的命令行界面,根据提示,一步步把缺失的进行安装和补充即可。

如果发现打开maui-check工具后,立即闪退,据说改用管理员权限重新打开终端再执行能解决这个问题。

如果接下来,你继续遇到了raw.githubusercontent.com:443的报错,要么全局代理,要么手动下载并且指定文件maui.manifest.json了。

maui-check -m maui.manifest.json

要命的是,如果你还是继续遇到raw.githubusercontent.com:443的报错,估计是在maui.manifest.json有个文件你过不去,最好也是手动下载并且写死本地路径最好。文件是:Versions.props

恭喜,这次算是过去了。

这里默认推荐你安装OpenJDK 11了,而且还是给你下载由微软构建的Microsoft OpenJDK 11

发现少了安卓SDK,那就Fix吧!

发现少了.net 6全新的工作负载.NET SDK - Workloads,那就Fix吧!

发现少了MAUI SDK全家桶,各个平台的都要,那就Fix吧!

终于,可以愉快的结束了,谢谢你哦!

再执行一次,看看全貌!确认下胜利的果实。

体验第一个MAUI项目

https://github.com/TaylorShi/HelloMaui

创建第一个MAUI项目“HelloMaui”

创建MAUI项目的方式有两种,一种是基于Visual Studio 2019创建,一种是基于DotNet Core CLI创建。

1. 基于Visual Studio 2019创建。

2. 基于DotNet Core CLI创建。

dotnet new maui -n HelloMaui

基于DotNet-Cli构建工具new关键词新建maui项目模板的项目,项目名称为HelloMaui

然后切换到项目目录

cd HelloMaui

用Visual Studio Code打开它。

code .

还原初创建项目

dotnet restore

构建初创项目

dotnet build -t:Run -f net6.0-android
dotnet build -t:Run -f net6.0-ios
dotnet build -t:Run -f net6.0-maccatalyst

如果是IOS项目,还可以选择模拟器:

dotnet build -t:Run -f net6.0-ios -p:_DeviceName=:v2:udid=<UDID>

运行第一个MAUI项目“HelloMaui”

折腾半天,发现用Visual Studio Code还是有点玩不装MAUI,还是转站Visual Studio 2019 Preview吧!

先用Visual Studio 2019 Preview打开Hello MaUI项目。

从结构上看,貌似是拆分成了WINUI项目和非WINUI项目,非WINUI项目主要是放Android、Linux、IOS平台的。

切换到Hello MAUI这个默认的选项后,运行,上来就是熟悉的,让你创建安卓模拟器配置了。

根据提示,乖乖就范吧,不然怕出什么幺蛾子。

等它下载完毕后,我们启动它,一般调试安卓都是要先开一个模拟器或者真机,这个流程我们还是知道的。

我的硬件还算争气,没出幺蛾子,直接就运行成功模拟器了。

接下来,根据实操经验,建议重启一次Visual Studio,这样它比较更好的识别我们的模拟器。

切换到Android Emulator模式,跑起来吧。

哈哈,不错,算是开始顺利,毕竟是成功部署进去了。

打量第一个MAUI项目“HelloMaui”

我们先顺着官方指引,看看MAUI对包的依赖。

<ItemGroup>
<PackageReference Include="Microsoft.Maui" Version="6.0.100-preview.5.794" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.ProjectReunion" Version="0.8.0-preview" />
<PackageReference Include="Microsoft.ProjectReunion.Foundation" Version="0.8.0-preview" />
<PackageReference Include="Microsoft.ProjectReunion.WinUI" Version="0.8.0-preview" />
<FrameworkReference Update="Microsoft.Windows.SDK.NET.Ref" RuntimeFrameworkVersion="10.0.19041.16" />
<FrameworkReference Update="Microsoft.Windows.SDK.NET.Ref" TargetingPackVersion="10.0.19041.16" />
</ItemGroup>

安卓模式下,能看到你的模拟器实例,到时候调试也是可以选择的。

关于应用的资源设置,我们也可以找到线索,可以看到貌似为了保持多个平台一致性,官方的案例里面就直接用SVG这种矢量格式了,其实这还是蛮值得推荐的,就是设计师要注意输出这种格式给开发了。

<ItemGroup>
<!-- App Icon -->
<MauiImage
Include="Resources\appicon.svg"
ForegroundFile="Resources\appiconfg.svg"
IsAppIcon="true"
Color="#512BD4" /> <!-- Splash Screen -->
<MauiSplashScreen Include="Resources\appiconfg.svg" Color="#512BD4" /> <!-- Images -->
<MauiImage Include="Resources\Images\*" /> <!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*" />
</ItemGroup>

针对不同平台的代码是分了文件夹放置的,这点和React Native还是挺类似的。

启动程序代码还是比较按最新的.Net Core的规范来,这里有个UseMauiApp,应该是指定应用的根视图的,加载字体的话,可以用ConfigureFonts来定制。

public class Startup : IStartup
{
public void Configure(IAppHostBuilder appBuilder)
{
appBuilder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
});
}
}

既然启动的时候,主动找了App,那么我们就看下App内部做了什么,这里设置了下资源文件目录,然后启动了一个Microsoft.Maui.Controls.Window类型的窗体。

public partial class App : Application
{
public App()
{
InitializeComponent();
} protected override IWindow CreateWindow(IActivationState activationState)
{
this.On<Microsoft.Maui.Controls.PlatformConfiguration.Windows>()
.SetImageDirectory("Assets"); return new Microsoft.Maui.Controls.Window(new MainPage());
}
}

前往MainPage,我们一看究竟,哇,这不就是对Windows开发童鞋最熟悉的Xaml,是不是很爽!这里只是控件全部基于Microsoft.Maui.Controls重新来了一套嘛,其实很熟悉了。

using System;
using Microsoft.Maui.Controls; namespace HelloMaui
{
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
} int count = 0;
private void OnCounterClicked(object sender, EventArgs e)
{
count++;
CounterLabel.Text = $"Current count: {count}";
}
}
}

我们再看看WINUI项目的内容,发现其实是个空壳子,所有的实现都不在这里,这里就是原来UWP那套应用配置项而已。

学习MAUI项目的基本玩法

1. 学习MAUI的启动入口规范。

首先.NET Multi-platform App UI (MAUI)它是遵循了.NET 通用主机(.NET Generic Host)的启动规范的,这个启动入口的设计,就在主项目的Startup.cs文件中。

在这个Startup.cs中必须存在一个对IStartup接口方法的实现,并且IAppHostBuilder必须至少添加一个应用的实现才可以跑起来,这里举例就是appBuilder.UseMauiApp<App>()这个。

using Microsoft.Maui;
using Microsoft.Maui.Hosting; public class Startup : IStartup
{
public void Configure(IAppHostBuilder appBuilder)
{
appBuilder.UseMauiApp<App>();
}
}

然后在App中,又必须存在一个对Application方法的继承实现,并且重写CreateWindow方法,而且至少实现一个Window的创建返回。

using Microsoft.Maui;
using Microsoft.Maui.Controls; public partial class App : Application
{
protected override IWindow CreateWindow(IActivationState activationState)
{
return new Window(new MainPage());
}
}

这里举例的MainPage窗体,是一个继承自ContentPage的实现者。

public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
}

2. 在MAUI中玩转自定义字体

如果需要注册新的字体加到应用中,可以在Startup.csConfigure方法中,通过ConfigureFonts来添加。

using Microsoft.Maui;
using Microsoft.Maui.Hosting; public class Startup : IStartup
{
public void Configure(IAppHostBuilder appBuilder)
{
appBuilder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("Lobster-Regular.ttf", "Lobster");
});
}
}

IFontCollection对象的AddFont方法中,第一个参数是字体的全称路径,第二个参数是这个字体的别名,并且所有定制字体都必须描述进项目的描述文件(.csproj)中,这里可以直接用目录 + *来包括整个目录的定制字体。

<ItemGroup>
<MauiFont Include="Resources\Fonts\*" />
</ItemGroup>

添加的字体,可以直接在Xaml里面,通过指定FontFamily来使用,可以使用无格式后缀的文件名全称,也可以用字体的别名。

<!-- Use font name -->
<Label Text="Hello .NET MAUI"
FontFamily="Lobster-Regular" />
<!-- Use font alias -->
<Label Text="Hello .NET MAUI"
FontFamily="Lobster" />

说了这么多,还是找个自定义字体试试,这里想到了自定义图标字体,从https://icofont.com/icons 找了一组品牌的图标字体,里面有微软的图标,下载后把ttf文件重命名下为Brand-IconFont.ttf,然后拖到项目的\Resources\Fonts文件夹下即可。

Startup.cs中,添加对这个字体的注册,别名取为BrandIcoFont吧。

public void Configure(IAppHostBuilder appBuilder)
{
appBuilder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("Brand-IconFont.ttf", "BrandIcoFont");
});
}

切换到构建界面的MainPage.xaml,找个位置把使用字体的文本控件插进去。

<Label
Grid.Row="2"
FontFamily="BrandIcoFont"
FontSize="32"
HorizontalOptions="CenterAndExpand"
SemanticProperties.HeadingLevel="Level1"
Text="" />

这里我们用别名的方式指定FontFamily为BrandIcoFont,至于Text的值,我们需要从字体网站获取下,一般复制HTML Entity的值就好了。

好了,跑起来,看看效果吧,还行,基本达到预期,看到微软Logo了。

3. 在MAUI中玩转自定义事件。

如果需要将控件的自定义事件加到应用中,可以在Startup.csConfigure方法中,通过ConfigureMauiHandlers来添加。

using Microsoft.Maui;
using Microsoft.Maui.Hosting; public class Startup : IStartup
{
public void Configure(IAppHostBuilder appBuilder)
{
appBuilder
.UseMauiApp<App>()
.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler(typeof(MyEntry), typeof(MyEntryHandler));
});
}
}

IMauiHandlersCollection对象的AddHandler方法中,第一个参数是控件的实体名称,第二个参数是需要绑定的自定义事件。

这样注册之后,所有MyEntry控件就都会绑定MyEntryHandler这个事件了。

4. 在MAUI中玩转自定义渲染器。

如果需要将控件的自定义渲染器加到应用中,可以在Startup.csConfigure方法中,通过ConfigureMauiHandlers来添加。

using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Controls.Compatibility; public class Startup : IStartup
{
public void Configure(IAppHostBuilder appBuilder)
{
appBuilder
.UseMauiApp<App>()
#if __ANDROID__
.ConfigureMauiHandlers(handlers =>
{
handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.BoxView),
typeof(Microsoft.Maui.Controls.Compatibility.Platform.Android.BoxRenderer));
handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.Frame),
typeof(Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers.FrameRenderer));
});
#elif __IOS__
.ConfigureMauiHandlers(handlers =>
{
handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.BoxView),
typeof(Microsoft.Maui.Controls.Compatibility.Platform.iOS.BoxRenderer));
handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.Frame),
typeof(Microsoft.Maui.Controls.Compatibility.Platform.iOS.FrameRenderer));
});
#endif
}
}

这里可以通过跨平台设备的if条件,来编写,通过IMauiHandlersCollection对象的AddCompatibilityRenderer方法来添加指定的控件走什么渲染器。

注意要添加using Microsoft.Maui.Controls.Compatibility;

using Microsoft.Maui;
using Microsoft.Maui.Hosting;
using Microsoft.Maui.Controls.Hosting;
using Microsoft.Maui.Controls.Compatibility; namespace HelloMaui
{
public class Startup : IStartup
{
public void Configure(IAppHostBuilder appBuilder)
{
appBuilder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("Brand-IconFont.ttf", "BrandIcoFont");
})
#if __ANDROID__
.ConfigureMauiHandlers(handlers =>
{
handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.BoxView),
typeof(Microsoft.Maui.Controls.Compatibility.Platform.Android.BoxRenderer));
handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.Frame),
typeof(Microsoft.Maui.Controls.Compatibility.Platform.Android.FastRenderers.FrameRenderer));
});
#elif __IOS__
.ConfigureMauiHandlers(handlers =>
{
handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.BoxView),
typeof(Microsoft.Maui.Controls.Compatibility.Platform.iOS.BoxRenderer));
handlers.AddCompatibilityRenderer(typeof(Microsoft.Maui.Controls.Frame),
typeof(Microsoft.Maui.Controls.Compatibility.Platform.iOS.FrameRenderer));
});
#endif
}
}
}

5. 在MAUI中玩转自定义控件。

.NET多平台应用UI(MAUI)提供可用于显示数据、启动操作、指示活动、显示集合、拾取数据等的控件集合。默认情况下,处理程序将这些跨平台控件映射到每个平台上的原生控件。例如,在iOS上,Button控件将会映射成UIButton。在安卓系统上,Button控件将会映射成AppCompatButton

比如我们要自定义安卓平台,所有的界面背景颜色。

using Microsoft.Maui;
using Microsoft.Maui.Controls; public partial class App : Application
{
public App()
{
InitializeComponent(); #if __ANDROID__
Microsoft.Maui.Handlers.ViewHandler.ViewMapper[nameof(IView.BackgroundColor)] = (h, v) =>
{
(h.NativeView as Android.Views.View).SetBackgroundColor(Microsoft.Maui.Graphics.Colors.Cyan.ToNative());
};
#endif
}
}

如果要移除安卓组件的下划线

using Microsoft.Maui;
using Microsoft.Maui.Controls; public partial class MainPage : ContentPage, IPage
{
public MainPage()
{
InitializeComponent();
#if __ANDROID__
Handlers.EntryHandler.EntryMapper[nameof(IEntry.BackgroundColor)] = (h, v) =>
{
(h.NativeView as global::Android.Views.Entry).UnderlineVisible = false;
};
#endif
}
}

如果想自定义控件,可以继承自Entry来做。

using Microsoft.Maui.Controls;

namespace HelloMaui
{
public class MyEntry : Entry
{ }
}

还可以自定义一个处理给到自定义控件。

using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Graphics; namespace MauiApp1
{
public partial class App : Application
{
public App()
{
InitializeComponent(); Microsoft.Maui.Handlers.EntryHandler.EntryMapper[nameof(IView.BackgroundColor)] = (handler, view) =>
{
if (view is MyEntry)
{
#if __ANDROID__
handler.NativeView.SetBackgroundColor(Colors.Red.ToNative());
#elif __IOS__
handler.NativeView.BackgroundColor = Colors.Red.ToNative();
handler.NativeView.BorderStyle = UIKit.UITextBorderStyle.Line;
#elif WINDOWS
handler.NativeView.Background = Colors.Red.ToNative();
#endif
}
};
}
}
}

参考

What is .NET MAUI?
.NET MAUI single project
.NET MAUI supported platforms
.NET MAUI installation
Build your first .NET MAUI app
.NET MAUI app startup
.NET MAUI single project
Customize .NET MAUI controls with handlers
Announcing .NET MAUI Preview 4
.NET 6 deep dive; what's new and what's coming
https://github.com/dotnet/maui
https://github.com/dotnet/net6-mobile-samples
Announcing .NET Multi-platform App UI Preview 3
Getting Started
https://github.com/redth/dotnet-maui-check
Introducing .NET Multi-platform App UI
The New .NET Multi-platform App UI
https://themesof.net
What is the difference between MAUI and WinUI?
https://github.com/redth/dotnet-maui-check
.Net MAUI检查命令maui-check闪退的问题和解决方案
.NET 6 亮点之工作负载,它是统一 .NET 的基础
如何评价 .NET 官方跨平台 UI 框架 MAUI?

乘风破浪,.Net Core遇见MAUI(.NET Multi-platform App UI),进击现代化跨设备应用框架的相关教程结束。

《乘风破浪,.Net Core遇见MAUI(.NET Multi-platform App UI),进击现代化跨设备应用框架.doc》

下载本文的Word格式文档,以方便收藏与打印。