乘风破浪,.Net Core遇见MAUI(.NET Multi-platform App UI),进击现代化跨设备应用框架
什么是MAUI
.NET Multi-platform App UI (MAUI) 的前身是Xamarin.Forms(适用于Android,iOS和UWP的跨平台移动优先框架),.NET MAUI是Xamarin.Forms的演进。我们拥有7年的为客户提供技术支持的经验,服务对象从独立开发人员到一些全球性的大公司,我们正在改善产品的核心功能,加快UI渲染,投资研发一致的系统设计模式,并从移动端扩展到桌面端。
与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,还需要额外安装两个扩展:
安装.NET 6 Preview
我们可以使用官方提供的一个工具来检查我们需要运行MAUI的环境是否齐全,还是很贴心哈。
安装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项目
创建第一个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.cs
的Configure
方法中,通过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.cs
的Configure
方法中,通过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.cs
的Configure
方法中,通过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 6 RC1 正式发布
- Update on .NET Multi-platform App UI (.NET MAUI)