自从6月份我们预览了Native Forms以来,我们一直在努力平衡这些不足之处,并通过修复错误并整合来自社区的优秀反馈来为Xamarin.Forms 2.5.0打磨它。
上个月在Microsoft Connect(); 在纽约,在主题演讲中使用了几个原生形式的例子。 最值得注意的是,James Montemagno演示了向开源Kickstarter iOS应用程序添加一个Xamarin.Forms页面。 您可以在Channel9上观看整个主题演讲。

另一个例子是SmartHotel360应用程序套件,它包括一个维护应用程序,它使用Native Forms将Xamarin.Forms页面复合到本机Xamarin.iOS和Xamarin.Android应用程序中。 这些应用程序的完整代码将很快在GitHub上提供。
本文将介绍这些更改,并为您开始将Native Forms嵌入到您自己的应用程序中作为指导。


本地Forms是什么?

本机形式允许您在本机Xamarin.iOS,Xamarin.Android或UWP应用程序中使用Xamarin.Forms ContentPage。
但是,为什么你可能会问,我想这样做? 让我们考虑一些情况:


Existing Xamarin.Forms Reuse

也许你在之前的Xamarin.Forms项目中写了一个服务的登录页面。 现在,您正在开发一个使用相同服务的本地Xamarin项目。 而不是再次编写该页面,您可以直接从Forms项目中直接重用它。

Migrate Xamarin.Forms to Xamarin Native

假设您使用Xamarin.Forms来快速创建原型或概念验证应用程序。 对于第二版,你已经决定在iOS上,你需要本土化。 不过,对于从原生应用程序的原型中重新编写每一页的前景,你并不感到兴奋。 使用Native Forms,您可以重用原型中的页面,只重写您绝对必须的页面。

Mix Xamarin.Forms into Xamarin Native Projects

您正在处理多个本地项目,并面临三次编写相同的“设置”和“关于”页面的前景。 停止! 只需将它们写入Xamarin.Forms项目并将其嵌入到您的本机应用程序中即可!

简单场景

为了显示原生形式,让我们通过一些代码来演示一个场景。 这个例子的完整代码可以在GitHub上找到。
假设我正在为UWP,Android和iOS创建全新的本机应用程序。 在每一个,我想添加一个小费计算器,但我真的不想从头开始写三个小费计算器。 幸运的是,Charles Petzold已经在Xamarin.Forms中写了一个,所以我所要做的就是将其嵌入到每个本地应用程序中。
在演示解决方案中,我创建了三个本地项目,另外还有一个TipCalc代码项目:


SolutionSolution

在我的TipCalc项目中,我简单地复制了原始的TipCalc解决方案的PCL项目中的代码。 代码的工作原理没有任何改变,但在我的项目中,我已经更新了一些过时的XAML,并添加了一些MessagingCenter代码,稍后我将会讨论这些代码。
要将代码嵌入到每个本机应用程序中,我需要添加对TipCalc项目的引用并安装Xamarin.Forms NuGet包。 一旦我这样做了,我在每个本地项目中的引用将如下所示:


iOS project referencesiOS project references

在每个本地项目可以嵌入任何Xamarin.Forms页面之前,他们需要通过调用Forms.Init()来初始化窗体。当一个本地应用程序,这是由你自己决定,只要它发生在构建表单ContentPage的任何实例之前。您可以选择在启动时(例如,在UIApplicationDelegate.FinishedLaunching()或Activity.OnCreate())期间执行此操作,或者您可能要等到构建第一个ContentPage之前。您的正确选择主要取决于您的应用程序流程中最方便的时间。
在Xamarin.Forms被初始化之后,只需要设置导航到TipCalcPage。本机表单向Xamarin.Forms中添加了一些扩展方法,该方法将ContentPage转换为适当的本机类型。
例如,在本地iOS项目中,通常使用带有UIViewController的UINavigationController.PushViewController()。 Native Forms为ContentPage提供了一个扩展,它将页面转换为一个UIViewContoller。导航到小费计算器的方法可能很简单:

点击(此处)折叠或打开

  1. public void NavigateToTipCalc()
  2. {
  3.     _navigation.PushViewController(new TipCalcPage().CreateViewController(), true);
  4. }
其中_navigation是一个UINavigationController。 而已; 从“提示计算器”按钮调用该方法,应用程序导航到Xamarin.Forms页面。


Navigating to the embedded Forms page on iOSNavigating to the embedded Forms page on iOS
在Android上,Native Forms提供了CreateFragment()和CreateSupportFragment()方法。 这些会将ContentPage转换为适合导航的片段。 例如,像这样的方法添加到FragmentActivity: Navigating to the embedded Forms page on AndroidNavigating to the embedded Forms page on Android
对于本地UWP应用程序,导航通常是使用Frame类来完成的; Frame.Navigate()方法接受一个Page类型并导航到它的一个实例。 本机窗体提供了一个类似的扩展框架,它需要一个ContentPage实例。 导航可以像下面这样简单:

点击(此处)折叠或打开

  1. private void NavigateToTipCalc()
  2. {
  3.     Frame.Navigate(new TipCalcPage());
  4. }

Navigating to the embedded Forms page on UWPNavigating to the embedded Forms page on UWP
正如你所看到的,使用Native Forms不需要太多的代码。

处理沟通

机会很好,你还需要在本地上下文和应用程序的Xamarin.Forms部分之间进行通信。 如果您正在重用另一个项目中的Xamarin.Forms代码,那么您可能已经使用任何事件聚合器,消息总线或其他方法对堆栈进行了设置。 但是,如果您还没有到位,Xamarin.Forms附带了一个内置的消息服务(称为MessagingCenter),可用于连接本机应用程序和它们托管的Xamarin.Forms页面之间的通信。
在这个演示解决方案中,我已经修改了TipCalc项目,允许它使用MessagingCenter向其主机应用程序发送和接收数据。 我已经定义了消息和他们的论点:

点击(此处)折叠或打开

  1. public static class Messages {
  2.     public static object Sender = new object();
  3.     public const string InitialAmount = "InitialAmount";
  4.     public const string Tip = "Tip";
  5. }
  6.  
  7. public class InitialAmountArgs {
  8.     public InitialAmountArgs(double initialAmount) {
  9.         InitialAmount = initialAmount;
  10.     }
  11.  
  12.     public double InitialAmount { get; }
  13. }
  14.  
  15. public class TipArgs {
  16.     public TipArgs(double tip) {
  17.         Tip = tip;
  18.     }
  19.  
  20.     public double Tip { get; }
  21. }
有了这个基础架构,原生应用程序很容易为小费计算器指定初始金额。 每个本机应用程序都有一个输入初始金额的字段; 当用户更新该字段时,将发送一条消息:

点击(此处)折叠或打开

  1. MessagingCenter.Send(TipCalc.Messages.Sender, TipCalc.Messages.InitialAmount,
  2.     new InitialAmountArgs(InitialAmount));

TipCalc项目(TipCalcModel)中的视图模型会监听该消息并作出相应的反应:

点击(此处)折叠或打开

  1. MessagingCenter.Subscribe<object, InitialAmountArgs>(this, Messages.InitialAmount, (s, e) =>
  2. {
  3.     SubTotal = e.InitialAmount;
  4. });
原生应用程序对计算的提示值的变化做出响应也很简单。 每当TipCalcModel中的TipAmount发生更改时,都会发送一条消息:

点击(此处)折叠或打开

  1. MessagingCenter.Send(Messages.Sender, Messages.Tip, new TipArgs(value));

本地应用程序可以侦听该消息并作出反应; 例如,iOS应用程序设置UILabel的文本:

点击(此处)折叠或打开

  1. MessagingCenter.Subscribe<object, TipArgs>(TipCalc.Messages.Sender, TipCalc.Messages.Tip,
  2.     (obj, args) => TipAmount.Text = args.Tip.ToString("C"));

这里是在UWP上的应用程序。 用户输入传递给小费计算器的初始金额,并将小费计算器的结果中继回到本地页面:



Communication on UWPCommunication on UWP

只是页面?

精明的读者会注意到,扩展方法都提供了不一定需要完整页面导航的返回类型。 也可以使用UIViewController,FrameworkElement和Fragment作为页面的一部分而不是全屏。 我们之前展示了如何在Flyout内部的UWP上显示ContentPage,而不是整页。
从理论上讲,可以使用这些方法在每个平台上嵌入一个ContentPage作为更大页面的一部分。 截至目前,这是一个官方不受支持的用例。 这就是说,如果你尝试了这个用例,我很想听听它是如何工作的。

今天更新到2.5.0!

您现在可以使用我们最新的稳定版本Xamarin.Forms 2.5.0,在NuGet上使用Native Forms。