紧急情况已经出现。任何玩过上一章的MonkeyTap游戏的人都会很快得出结论,它迫切需要一个非常基本的增强功能,而且它根本不可能在没有它的情况下存在。
MonkeyTap需要声音。
它不需要非常复杂的声音 - 伴随着四个BoxView元素的闪光,只需要几声嘟嘟声。但是Xamarin.Forms API不支持声音,所以声音不是我们可以通过几个API调用添加到MonkeyTap的东西。支持声音需要有点像Xamarin.Forms来利用平台特定的声音生成设施。弄清楚如何在iOS,Android和Windows Phone中发出声音是很难的。但是,Xamarin.Forms程序如何调用各个平台呢?
在解决声音的复杂性之前,让我们通过一个更简单的例子来研究制作平台特定API调用的不同方法。本章中显示的前三个短程序在功能上完全相同:它们都显示由底层平台操作系统提供的两个微小信息项,显示运行程序的设备模型和操作系统版本。
共享资产项目中的预处理
正如您在第2章“应用程序剖析”中所了解到的,您可以使用共享资产项目(SAP)或可移植类库(PCL)来获取所有三个平台通用的代码。 SAP包含在平台项目之间共享的代码文件,而PCL将公共代码包含在只能通过公共类型访问的库中。
从共享资产项目访问平台API比从可移植类库更简单,因为它涉及更传统的编程工具,所以让我们首先尝试这种方法。 您可以使用第2章中描述的过程使用SAP创建Xamarin.Forms解决方案。然后,您可以将基于XAML的ContentPage类添加到SAP,就像将其添加到PCL一样。
这是显示平台信息的项目的XAML文件,名为PlatInfoSap1:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="PlatInfoSap1.PlatInfoSap1Page">
<StackLayout Padding="20">
<StackLayout VerticalOptions="CenterAndExpand">
<Label Text="Device Model:" />
<ContentView Padding="50, 0, 0, 0">
<Label x:Name="modelLabel"
FontSize="Large"
FontAttributes="Bold" />
</ContentView>
</StackLayout>
<StackLayout VerticalOptions="CenterAndExpand">
<Label Text="Operating System Version:" />
<ContentView Padding="50, 0, 0, 0">
<Label x:Name="versionLabel"
FontSize="Large"
FontAttributes="Bold" />
</ContentView>
</StackLayout>
</StackLayout>
</ContentPage>
代码隐藏文件必须为modelLabel和versionLabel设置Text属性。
共享资产项目中的代码文件是各个平台中代码的扩展。 这意味着SAP中的代码可以使用C#预处理程序指令#if,#elif,#else和#endif以及为这三个平台定义的条件编译符号,如第2章和第4章所示。这些符号是:
- IOS 对应 iOS
- ANDROID 对应 Android
- WINDOWS_UWP 对应 Universal Windows Platform
- WINDOWS_APP 对应 Windows 8.1
- WINDOWS_PHONE_APP 对应 Windows Phone 8.1
当然,获取模型和版本信息所涉及的API对于三个平台是不同的:
- 对于iOS,请使用UIKit名称空间中的UIDevice类。
- 对于Android,请在Android.OS命名空间中使用Build类的各种属性。
- 对于Windows平台,请使用Windows.Security.ExchangeActiveSyncProvisioning命名空间中的EasClientDeviceInformation类。
这是PlatInfoSap1.xaml.cs代码隐藏文件,显示了如何根据条件编译符号设置modelLabel和versionLabel:
using System;
using Xamarin.Forms;
#if __IOS__
using UIKit;
#elif __ANDROID__
using Android.OS;
#elif WINDOWS_APP || WINDOWS_PHONE_APP || WINDOWS_UWP
using Windows.Security.ExchangeActiveSyncProvisioning;
#endif
namespace PlatInfoSap1
{
public partial class PlatInfoSap1Page : ContentPage
{
public PlatInfoSap1Page ()
{
InitializeComponent ();
#if __IOS__
UIDevice device = new UIDevice();
modelLabel.Text = device.Model.ToString();
versionLabel.Text = String.Format("{0} {1}", device.SystemName,
device.SystemVersion);
#elif __ANDROID__
modelLabel.Text = String.Format("{0} {1}", Build.Manufacturer,
Build.Model);
versionLabel.Text = Build.VERSION.Release.ToString();
#elif WINDOWS_APP || WINDOWS_PHONE_APP || WINDOWS_UWP
EasClientDeviceInformation devInfo = new EasClientDeviceInformation();
modelLabel.Text = String.Format("{0} {1}", devInfo.SystemManufacturer,
devInfo.SystemProductName);
versionLabel.Text = devInfo.OperatingSystem;
#endif
}
}
}
请注意,这些预处理程序指令用于选择不同的using指令以及调用特定于平台的API。 在像这样简单的程序中,您可以简单地将命名空间包含在类名中,但是对于更长的代码块,您可能希望使用指令。
当然它有效:
这种方法的优点是您可以在一个位置拥有三个平台的所有代码。 但代码清单中的预处理程序指令 - 让我们面对它 - 相当丑陋,它们又回到了编程的早期时代。 使用预处理程序指令对于简短且不太频繁的调用(例如此示例)可能看起来不那么糟糕,但是在较大的程序中,您需要处理特定于平台的代码块和共享代码,并且可以轻松地执行大量预处理程序指令? 变得混乱。 预处理程序指令应该用于少量修复,通常不作为应用程序中的结构元素。
让我们尝试另一种方法。