特定于平台的位图
如您所见,您可以通过Web或共享PCL项目加载位图。您还可以加载存储为各个平台项目中的资源的位图。这个工作的工具是Image?Source.FromFile静态方法和相应的FileImageSource类。
您可能会将此工具主要用于与用户界面元素连接的位图。 MenuItem和ToolBarItem中的Icon属性的类型为FileImageSource。 Button中的Image属性也是FileImageSource类型。
FileImageSource的另外两个用法将不在本章中讨论:Page类定义了FileImageSource类型的Icon属性和string类型的BackgroundImage属性,但它被假定为存储在平台项目中的位图的名称。
位图在各个平台项目中的存储允许高水平的平台特异性。您可能认为通过在PCL项目中存储每个平台的位图并使用Device.OnPlatform方法或OnPlatform类来选择它们,您可以获得相同程度的平台特异性。但是,正如您很快就会发现的那样,所有三个平台都可以存储不同像素分辨率的位图,然后自动访问最佳像素分辨率。只有当各个平台本身加载位图时,才能利用这一有价值的功能,只有在使用ImageSource.FromFile和FileImageSource时才会出现这种情况。
新创建的Xamarin.Forms解决方案中的平台项目已包含多个位图。 在iOS项目中,您可以在Resources文件夹中找到它们。 在Android项目中,它们位于Resources文件夹的子文件夹中。 在各种Windows项目中,它们位于Assets文件夹和子文件夹中。 这些位图是应用程序图标和启动屏幕,当您准备将应用程序推向市场时,您将需要替换它们。
让我们编写一个名为PlatformBitmaps的小项目,它从每个平台项目中访问一个应用程序图标,并显示Image元素的渲染大小。 如果您正在使用FileImageSource加载位图(如此程序那样),则需要将File属性设置为包含位图文件名的字符串。 几乎总是,您将在代码中使用Device.OnPlatform或在XAML中使用OnPlatform来指定三个文件名:
public class PlatformBitmapsPage : ContentPage
{
public PlatformBitmapsPage()
{
Image image = new Image
{
Source = new FileImageSource
{
File = Device.OnPlatform(iOS: "Icon-Small-40.png",
Android: "icon.png",
WinPhone: "Assets/StoreLogo.png")
},
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};
Label label = new Label
{
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.CenterAndExpand
};
image.SizeChanged += (sender, args) =>
{
label.Text = String.Format("Rendered size = {0} x {1}",
image.Width, image.Height);
};
Content = new StackLayout
{
Children =
{
image,
label
}
};
}
}
当您访问存储在iOS项目的Resources文件夹或Android项目的Resources文件夹(或子文件夹)中的位图时,请不要在文件名前添加文件夹名称。 这些文件夹是这些平台上位图的标准存储库。 但位图可以位于Windows或Windows Phone项目中的任何位置(包括项目根目录),因此需要文件夹名称(如果有)。
在所有三种情况下,默认图标是着名的六边形Xamarin徽标(俗称Xamagon),但每个平台的图标大小都有不同的约定,因此渲染的大小不同:
如果你开始探索iOS和Android项目中的图标位图,你可能会有点融合:在iOS和Android项目中似乎有多个具有相同名称(或类似名称)的位图。
是时候深入研究位图分辨率的主题了。
位图分辨率
在PlatformBitmaps中指定的iOS位图文件名是Icon-Small-40.png,但是如果查看iOS项目的Resources文件夹,您将看到三个具有该名称变体的文件。 它们都有不同的尺寸:
- Icon-Small-40.png - 40像素的正方形
- Icon-Small-40@2x.png - 80像素的正方形
- Icon-Small-40@3x.png - 120像素的正方形
正如您在本章前面所发现的,当Image是StackLayout的子节点时,iOS会在其像素大小中显示位图,并在位图像素和屏幕像素之间进行一对一映射。这是位图的最佳显示。
但是,在屏幕截图中使用的iPhone 6模拟器上,Image的渲染大小为40个独立单元。在iPhone 6上,每个与设备无关的单元有两个像素,这意味着该屏幕截图中显示的实际位图不是Icon-Small-40.png而是Icon?Small-40@2x.png,这是两倍40 ,或80像素的正方形。
如果你在iPhone 6 Plus上运行程序 - 它有一个与设备无关的单位等于三个像素 - 你将再次看到渲染大小为40像素,这意味着Icon-Small-40@3x.png位图被展示。现在在iPad 2模拟器上试一试。 iPad 2的屏幕尺寸仅为768×1024,与设备无关的单位与像素相同。现在显示Icon-Small-40.png位图,渲染的大小仍为40像素。
这就是你想要的。您希望能够在设备独立单元中控制位图的渲染大小,因为这样就可以在不同的设备和平台上实现可感知的类似位图大小。当您指定Icon-Small-40.png位图时,您希望该位图在所有iOS设备上呈现为40个与设备无关的单位 - 或大约四分之一英寸。但如果程序在Apple Retina设备上运行,则不希望将40像素的方块位图拉伸为40个独立的单元。为了获得最大的视觉保真度,您需要显示更高分辨率的位图,并将位图像素与屏幕像素进行一对一的映射。
如果您查看Android资源目录,您将找到名为icon.png的四个不同版本的位图。这些存储在Resources的不同子文件夹中:
- drawable/ icon.png - 72像素的正方形
- drawable-hdpi / icon.png - 72像素的正方形
- drawable-xdpi / icon.png - 96像素的正方形
- drawable-xxdpi / icon.png - 144像素的正方形
无论Android设备如何,图标都会以48个与设备无关的单位进行渲染。在屏幕截图中使用的Nexus 5上,与设备无关的单元有三个像素,这意味着该屏幕上实际显示的位图是drawable-xxdpi文件夹中的位图,即144像素的正方形。
iOS和Android的优点在于您只需要提供各种大小的位图 - 并为它们提供正确的名称或将它们存储在正确的文件夹中 - 操作系统会为设备的特定分辨率选择最佳图像。
Windows运行时平台具有类似的功能。在UWP项目中,您将看到包含scale-200的文件名;例如,Square150x150Logo.scale-200.png。单词缩放后的数字是一个百分比,虽然文件名似乎表明这是一个150×150的位图,但图像实际上是两倍大:300×300。在Windows项目中,您将看到包含scale-100的文件名,在WinPhone项目中,您将看到scale-240。
但是,您已经看到Windows运行时的Xamarin.Forms以独立的独立大小显示位图,您仍然需要稍微区别对待Windows平台。但是在所有三个平台上,您可以控制与设备无关的单元中位图的大小。
创建自己的平台特定图像时,请遵循以下三个部分中的准则。
适用于iOS的设备无关位图
位图的iOS命名方案涉及文件名的后缀。 操作系统根据设备的近似像素分辨率获取具有基础文件名的特定位图:
- 160DPI设备无后缀(与设备无关的单位为1像素)
- 320DPI设备的@ 2x后缀(DIU为2像素)
- @ 3x后缀:480 DPI设备(DIU 3个像素)
例如,假设您希望名为MyImage.jpg的位图在屏幕上显示为大约1平方英寸。 您应该提供此位图的三个版本:
- MyImage.jpg - 160像素的正方形
- MyImage@2x.jpg - 320像素的正方形
- MyImage@3x.jpg - 480像素的正方形
位图将呈现为160个与设备无关的单元。 对于小于1英寸的渲染尺寸,按比例减小像素。
创建这些位图时,请从最大的位图开始。 然后,您可以使用任何位图编辑实用程序来减小像素大小。 对于某些图像,您可能希望微调或完全重绘较小的版本。
您可能已经注意到在检查Xamarin.Forms模板包含的iOS项目的各种图标文件时,并非所有三个分辨率都包含每个位图。 如果iOS找不到具有所需特定后缀的位图,它将退回并使用其他位图,在此过程中向上或向下缩放位图。
适用于Android的设备无关位图
对于Android,位图存储在资源的各个子文件夹中,这些子文件夹对应于屏幕的像素分辨率。 Android为六种不同级别的设备解析定义了六种不同的目录名称:
- 120DPI设备的drawable-ldpi(低DPI)(DIU为0.75像素)
- 可绘制-mdpi(中),用于160 DPI设备(1个像素到DIU)
- 240DPI设备的drawable-hdpi(高)(DIU为1.5像素))
- 320DPI设备的drawable-xhdpi(超高)(DIU为2像素)
- 480DPI设备的drawable-xxhdpi(额外超高)(DIU为3像素)
- 440DPI设备的drawable-xxxhdpi(三个额外高)(DIU为4个像素)
如果您希望在屏幕上将名为MyImage.jpg的位图渲染为1英寸的正方形,则可以在所有这些目录中使用相同的名称提供最多六个版本的此位图。 这个1英寸方形位图的大小(以像素为单位)等于与该目录关联的DPI:
- drawable-ldpi / MyImage.jpg - 120像素的正方形
- drawable-mdpi / MyImage.jpg - 160像素的正方形
- drawable-hdpi / MyImage.jpg - 240像素的正方形
- drawable-xhdpi / MyImage.jpg - 320像素的正方形
- drawable-xxdpi / MyImage.jpg - 480像素的正方形
- drawable-xxxhdpi / MyImage.jpg - 640像素的正方形
位图将呈现为160个与设备无关的单元。
您不需要为所有六种分辨率创建位图。 由Xamarin.Forms模板创建的Android项目仅包括drawable-hdpi,drawable-xhdpi和drawable-xxdpi,以及不带后缀的不必要的drawable文件夹。 这些包含最常见的设备。 如果Android操作系统找不到所需分辨率的位图,它将回退到可用的大小并进行缩放。
适用于Windows运行时平台的设备无关位图
Windows运行时支持位图命名方案,该方案允许您嵌入每个设备无关单位的像素比例因子,以百分比表示。 例如,对于针对具有两个像素的设备的1英寸方形位图,请使用以下名称:
- MyImage.scale-200.jpg - 320像素的正方形
Windows文档不清楚您可以使用的实际百分比。 构建程序时,有时您会在“输出”窗口中看到有关特定平台不支持的百分比的错误消息。
但是,鉴于Xamarin.Forms以与设备无关的大小显示Windows运行时位图,此工具在这些设备上的用途有限。
让我们来看一个实际上为三种平台提供各种大小的自定义位图的程序。这些位图旨在渲染大约一平方英寸,大约是纵向模式下手机屏幕宽度的一半。
这个ImageTap程序创建了一对基本的,可点击的按钮式对象,它们不是文本而是位图。 ImageTap创建的两个按钮可能会替代传统的“确定”和“取消”按钮,但也许您希望使用着名绘画中的面孔作为按钮。也许你想要OK按钮显示Botticelli的维纳斯的脸部和取消按钮,以显示Edvard Munch的The Scream中被困扰的男人。
在本章的示例代码中,名为Images的目录包含名为Venus_xxx.jpg和Scream_xxx.jpg的图像,其中xxx表示像素大小。每个图像有八种不同的尺寸:60,80,120,160,240,320,480和640像素的正方形。此外,一些文件的名称为Venus_xxx_id.jpg和Scream_xxx_id.jpg。这些版本的实际像素大小显示在图像的右下角,这样我们就可以在屏幕上看到操作系统选择的位图。
为避免混淆,首先将具有原始名称的位图添加到ImageTap项目折叠器中,然后在Visual Studio中重命名它们。
在iOS项目的Resources文件夹中,重命名了以下文件:
- Venus_160_id.jpg成为了Venus.jpg
- Venus_320_id.jpg成为Venus@2x.jpg
- Venus_480_id.jpg成为Venus@3x.jpg
这与Scream.jpg位图类似。
在Android项目Resources文件夹的各个子文件夹中,重命名了以下文件:
- Venus_160_id.jpg成为drawable-mdpi / Venus.jpg
- Venus_240_id.jpg成为drawable-hdpi / Venus.jpg
- Venus_320_id.jpg成为drawable-xhdpi / Venus.jpg
- Venus_480_id.jpg成为drawable_xxhdpi / Venus.jpg
同样适用于Scream.jpg位图。
对于Windows Phone 8.1项目,Venus_160_id.jpg和Scream_160_id.jpg文件被复制到Images文件夹并重命名为Venus.jpg和Scream.jpg。
Windows 8.1项目创建的可执行文件不在手机上运行,而是在平板电脑和桌面上运行。 传统上这些设备的分辨率为96英寸,因此Venus-_100_id.jpg和Scream_100_id.jpg文件被复制到Images文件夹并重命名为Venus.jpg和Scream.jpg。
UWP项目针对所有形状因子,因此将几个位图复制到Images文件夹并重命名,以便在手机上使用160像素的方形位图,并在平板电脑和桌面屏幕上使用100像素的方形位图:
- Venus_160_id.jpg成为了Venus.scale-200.jpg
- Venus_100_id.jpg成为Venus.scale-100.jpg
同样适用于Scream.jpg位图。
每个项目都需要针对这些位图执行不同的构建操作。当您将文件添加到项目时,应该自动设置此项,但您肯定要仔细检查以确保正确设置了构建操作:
- iOS:BundleResource
- Android:AndroidResource
- Windows运行时:内容
你不必记住这些。如有疑问,只需检查平台项目中Xamarin.Forms解决方案模板所包含的位图的构建操作。
ImageTap程序的XAML文件将两个Image元素中的每一个放在ContentView上,该内容从隐式样式变为白色。这个白色的ContentView完全由图像覆盖,但是(正如你所看到的)它会在程序闪烁图像时发出信号,表示它已被点击。
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ImageTap.ImageTapPage">
<StackLayout>
<StackLayout.Resources>
<ResourceDictionary>
<Style TargetType="ContentView">
<Setter Property="BackgroundColor" Value="White" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="VerticalOptions" Value="CenterAndExpand" />
</Style>
</ResourceDictionary>
</StackLayout.Resources>
<ContentView>
<Image>
<Image.Source>
<OnPlatform x:TypeArguments="ImageSource"
iOS="Venus.jpg"
Android="Venus.jpg"
WinPhone="Images/Venus.jpg" />
</Image.Source>
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="OnImageTapped" />
</Image.GestureRecognizers>
</Image>
</ContentView>
<ContentView>
<Image>
<Image.Source>
<OnPlatform x:TypeArguments="ImageSource"
iOS="Scream.jpg"
Android="Scream.jpg"
WinPhone="Images/Scream.jpg" />
</Image.Source>
<Image.GestureRecognizers>
<TapGestureRecognizer Tapped="OnImageTapped" />
</Image.GestureRecognizers>
</Image>
</ContentView>
<Label x:Name="label"
FontSize="Medium"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
</StackLayout>
</ContentPage>
XAML文件使用OnPlatform选择平台资源的文件名。请注意,OnPlatform的x:TypeArguments属性设置为ImageSource,因为此类型必须与目标属性的类型完全匹配,后者是Image的Source属性。 ImageSource定义了字符串到其自身的隐式转换,因此指定文件名就足够了。 (此隐式转换的逻辑首先检查字符串是否具有URI前缀。如果不是,则假定该字符串是平台项目中嵌入文件的名称。)
如果要避免在使用平台位图的程序中完全避免使用OnPlatform,可以将Windows位图放在项目的根目录中而不是文件夹中。
点击其中一个按钮可以做两件事:Tapped处理程序将Image的Opacity属性设置为0.75,这会导致部分显示白色ContentView背景并模拟闪光。计时器将不透明度恢复为十分之一秒的默认值。 Tapped处理程序还显示Image元素的渲染大小:
public partial class ImageTapPage : ContentPage
{
public ImageTapPage()
{
InitializeComponent();
}
void OnImageTapped(object sender, EventArgs args)
{
Image image = (Image)sender;
image.Opacity = 0.75;
Device.StartTimer(TimeSpan.FromMilliseconds(100), () =>
{
image.Opacity = 1;
return false;
});
label.Text = String.Format("Rendered Image is {0} x {1}",
image.Width, image.Height);
}
}
与位图上的像素大小相比,渲染大小确认三个平台确实选择了最佳位图:
这些按钮占据了所有三个平台上屏幕宽度的大约一半。 此大小调整完全基于位图本身的大小,代码或标记中没有任何其他大小调整信息。