Xamarin.Mac 中的图像
本文介绍如何在 Xamarin.Mac 应用程序中使用图像和图标。 它介绍了创建和维护创建应用程序图标所需的图像,以及在 C# 代码和 Xcode 的 Interface Builder 中使用图像。
概述
在 Xamarin.Mac 应用程序中使用 C# 和 .NET 时,可以访问开发人员处理 Objective-C 和 Xcode 时使用的同一图像和图标工具。
在 macOS(以前称为 Mac OS X)应用程序中使用图像资产有多种方法。 从仅将图像显示为应用程序的 UI 的一部分,将其分配给 UI 控件(如工具栏或源列表项),到提供图标,Xamarin.Mac 可通过以下方式轻松地向 macOS 应用程序添加出色的插图:
- UI 元素 - 图像可以显示为背景,也可以在图像视图 (
NSImageView
) 中作为应用程序的一部分显示。 - 按钮 - 图像可以显示在按钮 (
NSButton
) 中。 - 图像单元格 - 作为基于表的控件的一部分(
NSTableView
或NSOutlineView
),图像可用于图像单元格 (NSImageCell
)。 - 工具栏项 - 可以将图像作为图像工具栏项 (
NSToolbarItem
)添加到工具栏 (NSToolbar
)。 - 源列表图标 - 作为源列表的一部分(特殊格式的
NSOutlineView
)。 - 应用图标 - 可将一系列图像组合到
.icns
集中,并用作应用程序的图标。 有关详细信息,请参阅我们的应用程序图标文档。
此外,macOS 还提供一组可在应用程序中使用的预定义图像。
在本文中,我们将介绍在 Xamarin.Mac 应用程序中使用图像和图标的基础知识。 强烈建议先阅读 Hello, Mac 一文,特别是 Xcode 和 Interface Builder 简介和输出口和操作部分,因为其中介绍了我们将在本文中使用的关键概念和技术。
将图像添加到 Xamarin.Mac 项目
添加图像以便在 Xamarin.Mac 应用程序中使用时,开发人员可以通过多种方式将图像文件包含在项目的源中:
- 主项目树 [已弃用] - 可以直接将图像添加到项目树。 从代码调用存储在主项目树中的图像时,未指定文件夹位置。 例如:
NSImage image = NSImage.ImageNamed("tags.png");
。 - 资源文件夹 [已弃用] - 特殊的“资源”文件夹适用于将成为应用程序捆绑包的一部分的任何文件,例如图标、启动屏幕或常规图像(或者开发人员希望添加的任何其他图像或文件)。 从代码调用存储在 Resources 文件夹中的图像时,就像存储在主项目树中的图像一样,不会指定文件夹位置。 例如:
NSImage.ImageNamed("tags.png")
。 - 自定义文件夹或子文件夹 [已弃用] - 开发人员可以将自定义文件夹添加到项目源树,并将图像存储在其中。 添加文件的位置可以嵌套在子文件夹中,以进一步帮助组织项目。 例如,如果开发人员向项目添加了一个
Card
文件夹,并将Hearts
子文件夹添加到该文件夹中,则将图像 Jack.png 存储在Hearts
文件夹中,NSImage.ImageNamed("Card/Hearts/Jack.png")
将在运行时加载图像。 - 资产目录图像集 [首选] - 在 OS X El Capitan 中添加,资产目录图像集包含支持各种设备和应用程序缩放因子所需的图像的所有版本或表示形式。 而不是依赖于图像资产文件名(@1x、@2x)。
将图像添加到资产目录图像集
如上所述,资产目录图像集包含支持应用程序的各种设备和缩放因素所需的图像的所有版本或表示形式。 图像集使用资产编辑器指定哪些图像属于哪个设备和/或分辨率,而不是依赖图像资产文件名(请参阅上面的“分辨率独立图像”和“图像名词”)。
在 Solution Pad 中,双击 Assets.xcassets 文件将其打开以供编辑:
右键单击“资产列表”,然后选择“新建图像集”:
选择新图像集,此时会显示编辑器:
在这里,我们可以拖动每个不同设备和分辨率所需的图像。
双击“资产列表”中的新图像集的“名称”对其进行编辑:
添加到“图像集”中添加了一个特殊的“Vector”类,允许我们在 casset 中包括 PDF 格式化矢量图像,而不是在不同的分辨率中包含单个位图文件。 使用此方法,将为 @1x 分辨率(格式化为矢量 PDF 文件)提供单个向量文件,@2x 和 @3x 版本的文件将在编译时生成并包含在应用程序的捆绑包中。
例如,如果将一个 MonkeyIcon.pdf
文件作为资产目录的向量,分辨率为 150px x 150px,则编译后,以下位图资产将包含在最终应用捆绑包中:
- MonkeyIcon@1x.png - 150px x 150px 分辨率。
- MonkeyIcon@2x.png - 300px x 300px 分辨率。
- MonkeyIcon@3x.png - 450px x 450px 分辨率。
在资产目录中使用 PDF 矢量图像时,应考虑以下事项:
- 这不是完整的矢量支持,因为 PDF 将在编译时将光栅化为位图,并且位图在最终应用程序中提供。
- 在资产目录中设置图像后,无法调整图像的大小。 如果尝试调整图像的大小(在代码中或使用自动布局和大小类),图像将像任何其他位图一样扭曲。
在 Xcode 的 Interface Builder 中使用图像集时,只需从“属性检查器”的下拉列表中选择集的名称:
添加新的资产集合
在资产目录中处理图像时,有时可能需要创建新集合,而不是将所有图像添加到 Assets.xcassets 集合。 例如,在设计按需资源时。
若要向项目添加新的资产目录,请执行以下操作:
右键单击 Solution Pad 中的项目,然后选择“添加”>“新建文件...”。
选择“Mac”>“资产目录”,输入集合的名称,然后单击“新建”按钮:
在这里,可以使用与项目中自动包含的默认 Assets.xcassets 集合相同的方式处理集合。
将图像添加到资源
在 Xamarin.Mac 应用程序中(在 C# 代码中或来自 Interface Builder)中使用图像文件之前,需要将其作为捆绑资源包含在项目的“资源”文件夹中。 若要向项目添加文件,请执行以下操作:
右键单击 Solution Pad 中项目的“资源”文件夹,然后选择“添加”>“添加文件...”:
从“添加文件”对话框中,选择要添加到项目的图像文件,为“替代生成操作”选择
BundleResource
,然后单击“打开”按钮:如果文件尚未位于“资源”文件夹中,系统会询问是否要复制、移动或链接文件。 选择适合你的需求的每个选项,通常是复制:
新文件将包含在项目中并读取以供使用:
对所需的任何图像文件重复此过程。
可以将任何 png、jpg 或 pdf 文件用作 Xamarin.Mac 应用程序中的源图像。 在下一部分中,我们将介绍如何添加图像和图标的高分辨率版本以支持基于 Retina 的 Mac。
重要
如果要将“图像”添加到“资源”文件夹,可以将“替代生成操作”设置为“默认”。 此文件夹的默认生成操作为BundleResource
。
提供所有应用图形资源的高分辨率版本
添加到 Xamarin.Mac 应用程序的任何图形资产(图标、自定义控件、自定义光标、自定义插图等)除了其标准分辨率版本之外,还需要具有高分辨率版本。 这是必需的,以便在配备 Retina Display 的 Mac 计算机上运行时,应用程序将看起来最佳。
采用 @2x 命名约定
创建图像的标准和高分辨率版本时,请在 Xamarin.Mac 项目中包括图像对时遵循图像对的命名约定:
- Standard-Resolution - ImageName.filename-extension(示例:tags.png)
- High-Resolution - ImageName@2x.filename-extension(示例:tags@2x.png)
添加到项目后,它们将如下所示:
将图像分配给 Interface Builder 中的 UI 元素时,只需在 ImageName 中选取该文件即可。filename-extension 格式(示例:tags.png)。 在 C# 代码中使用图像时,可以在 ImageName 中选择该文件。filename-extension 格式。
在 Mac 上运行 Xamarin.Mac 应用程序时,ImageName。filename-extension 格式图像用于标准分辨率显示器,ImageName@2x.filename-extension 图像将针对配备 Retina Display 的 Mac 自动选取。
在 Interface Builder 中使用图像
对于添加到 Xamarin.Mac 项目中的 Resources 文件夹中的任何图像资源,若将生成操作设置为 BundleResource,将自动显示在 Interface Builder 中,并且可以选择为 UI 元素的一部分(如果处理图像)。
若要在 Interface Builder 中使用图像,请执行以下操作:
使用
BundleResource
的生成操作将图像添加到 Resources 文件夹:双击 Main.storyboard 文件将其打开,以便在 Interface Builder 中编辑:
将图像拖动到设计图面上的 UI 元素(例如,图像工具栏项):
在“图像名称”下拉列表中,选择已添加到“资源”文件夹的图像:
所选图像将显示在设计图面中:
保存更改并返回到 Visual Studio for Mac 以与 Xcode 同步。
上述步骤适用于允许在“属性检查器”中设置其图像属性的任何 UI 元素。 同样,如果已包含 图像文件的 @2x 版本,它将自动在配备 Retina Display 的 Mac 上使用。
重要
如果图像在“图像名称”下拉列表中不可用,请关闭 Xcode 中的 .storyboard 项目,然后从 Visual Studio for Mac 中重新打开它。 如果图像仍然不可用,请确保图像的生成操作已BundleResource
,而且图像已添加到“资源”文件夹。
在 C# 代码中使用图像
在 Xamarin.Mac 应用程序中使用 C# 代码将图像加载到内存中时,图像将存储在 NSImage
对象中。 如果图像文件已包含在 Xamarin.Mac 应用程序捆绑包(包含在资源中),请使用以下代码加载图像:
NSImage image = NSImage.ImageNamed("tags.png");
上面的代码使用 NSImage
类的静态 ImageNamed("...")
方法将给定图像加载到 Resources 文件夹中的内存中。如果找不到图像,将返回 null
。 与 Interface Builder 中分配的图像一样,如果已包含图像文件的 @2x 版本,它将自动在配备 Retina Display 的 Mac 上使用。
若要从应用程序捆绑包之外加载图像(从 Mac 文件系统),请使用以下代码:
NSImage image = new NSImage("/Users/KMullins/Documents/photo.jpg")
使用模板图像
根据 macOS 应用的设计,有时可能需要自定义用户界面中的图标或图像以匹配配色方案的更改(例如,基于用户首选项)。
若要实现此效果,请将图像资产的“呈现模式”切换到“模板图像”:
从 Xcode 的 Interface Builder 中,将图像资产分配给 UI 控件:
或者,可以在代码中设置图像源:
MyIcon.Image = NSImage.ImageNamed ("MessageIcon");
将以下公共函数添加到视图控制器:
public NSImage ImageTintedWithColor(NSImage sourceImage, NSColor tintColor)
=> NSImage.ImageWithSize(sourceImage.Size, false, rect => {
// Draw the original source image
sourceImage.DrawInRect(rect, CGRect.Empty, NSCompositingOperation.SourceOver, 1f);
// Apply tint
tintColor.Set();
NSGraphics.RectFill(rect, NSCompositingOperation.SourceAtop);
return true;
});
重要
特别是随着 macOS Mojave 中暗黑模式的出现,在重装自定义渲染的 NSImage
对象时避免使用 LockFocus
API 非常重要。 此类图像变为静态图像,不会自动更新以考虑外观或显示密度更改。
通过使用上述基于处理程序的机制,在托管 NSImage
时,动态条件的重新呈现将自动发生,例如,在 NSImageView
中。
最后,若要淡化模板图像,请针对图像调用此函数以着色:
MyIcon.Image = ImageTintedWithColor (MyIcon.Image, NSColor.Red);
将图像与表视图配合使用
若要在单元格中包含NSTableView
图像,需要更改表视图NSTableViewDelegate's
GetViewForItem
方法返回数据的方式,以使用NSTableCellView
而不是典型NSTextField
数据。 例如:
public override NSView GetViewForItem (NSTableView tableView, NSTableColumn tableColumn, nint row)
{
// This pattern allows you reuse existing views when they are no-longer in use.
// If the returned view is null, you instance up a new view
// If a non-null view is returned, you modify it enough to reflect the new data
NSTableCellView view = (NSTableCellView)tableView.MakeView (tableColumn.Title, this);
if (view == null) {
view = new NSTableCellView ();
if (tableColumn.Title == "Product") {
view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
view.AddSubview (view.ImageView);
view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
} else {
view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
}
view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
view.AddSubview (view.TextField);
view.Identifier = tableColumn.Title;
view.TextField.BackgroundColor = NSColor.Clear;
view.TextField.Bordered = false;
view.TextField.Selectable = false;
view.TextField.Editable = true;
view.TextField.EditingEnded += (sender, e) => {
// Take action based on type
switch(view.Identifier) {
case "Product":
DataSource.Products [(int)view.TextField.Tag].Title = view.TextField.StringValue;
break;
case "Details":
DataSource.Products [(int)view.TextField.Tag].Description = view.TextField.StringValue;
break;
}
};
}
// Tag view
view.TextField.Tag = row;
// Setup view based on the column selected
switch (tableColumn.Title) {
case "Product":
view.ImageView.Image = NSImage.ImageNamed ("tags.png");
view.TextField.StringValue = DataSource.Products [(int)row].Title;
break;
case "Details":
view.TextField.StringValue = DataSource.Products [(int)row].Description;
break;
}
return view;
}
这里有几点值得关注。 首先,对于要包含图像的列,我们将创建所需大小和位置的新 NSImageView
,我们还创建新的 NSTextField
,并根据是否使用图像来放置其默认位置:
if (tableColumn.Title == "Product") {
view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
view.AddSubview (view.ImageView);
view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
} else {
view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
}
其次,我们需要在父级 NSTableCellView
中包含新的图像视图和文本字段:
view.AddSubview (view.ImageView);
...
view.AddSubview (view.TextField);
...
最后,我们需要告诉文本字段,它可以使用表视图单元格收缩和增长:
view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
示例输出:
有关使用表视图的详细信息,请参阅表视图文档。
将图像与大纲视图配合使用
若要在单元格中包含NSOutlineView
图像,需要更改大纲视图NSTableViewDelegate's
GetView
方法返回数据的方式,以使用NSTableCellView
常规NSTextField
数据。 例如:
public override NSView GetView (NSOutlineView outlineView, NSTableColumn tableColumn, NSObject item) {
// Cast item
var product = item as Product;
// This pattern allows you reuse existing views when they are no-longer in use.
// If the returned view is null, you instance up a new view
// If a non-null view is returned, you modify it enough to reflect the new data
NSTableCellView view = (NSTableCellView)outlineView.MakeView (tableColumn.Title, this);
if (view == null) {
view = new NSTableCellView ();
if (tableColumn.Title == "Product") {
view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
view.AddSubview (view.ImageView);
view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
} else {
view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
}
view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
view.AddSubview (view.TextField);
view.Identifier = tableColumn.Title;
view.TextField.BackgroundColor = NSColor.Clear;
view.TextField.Bordered = false;
view.TextField.Selectable = false;
view.TextField.Editable = !product.IsProductGroup;
}
// Tag view
view.TextField.Tag = outlineView.RowForItem (item);
// Allow for edit
view.TextField.EditingEnded += (sender, e) => {
// Grab product
var prod = outlineView.ItemAtRow(view.Tag) as Product;
// Take action based on type
switch(view.Identifier) {
case "Product":
prod.Title = view.TextField.StringValue;
break;
case "Details":
prod.Description = view.TextField.StringValue;
break;
}
};
// Setup view based on the column selected
switch (tableColumn.Title) {
case "Product":
view.ImageView.Image = NSImage.ImageNamed (product.IsProductGroup ? "tags.png" : "tag.png");
view.TextField.StringValue = product.Title;
break;
case "Details":
view.TextField.StringValue = product.Description;
break;
}
return view;
}
这里有几点值得关注。 首先,对于要包含图像的列,我们将创建所需大小和位置的新 NSImageView
,我们还创建新的 NSTextField
,并根据是否使用图像来放置其默认位置:
if (tableColumn.Title == "Product") {
view.ImageView = new NSImageView (new CGRect (0, 0, 16, 16));
view.AddSubview (view.ImageView);
view.TextField = new NSTextField (new CGRect (20, 0, 400, 16));
} else {
view.TextField = new NSTextField (new CGRect (0, 0, 400, 16));
}
其次,我们需要在父级 NSTableCellView
中包含新的图像视图和文本字段:
view.AddSubview (view.ImageView);
...
view.AddSubview (view.TextField);
...
最后,我们需要告诉文本字段,它可以使用表视图单元格收缩和增长:
view.TextField.AutoresizingMask = NSViewResizingMask.WidthSizable;
示例输出:
有关使用大纲视图的详细信息,请参阅我们的大纲视图文档。
总结
本文详细介绍了如何使用 Xamarin.Mac 应用程序中的图像和图标。 我们了解了图像的不同类型和用法,如何在 Xcode 的 Interface Builder 中使用图像和图标,以及如何在 C# 代码中使用图像和图标。