Xamarin.Forms 资源字典
ResourceDictionary
是 Xamarin.Forms 应用程序使用的资源的存储库。 存储在 ResourceDictionary
中的典型资源包括样式、控件模板、数据模板、颜色和转换器。
在 XAML 中,可以使用 StaticResource
或 DynamicResource
标记扩展来引用存储在 ResourceDictionary
中的资源并将其应用于元素。 在 C# 中,还可以在 ResourceDictionary
中定义资源,然后使用基于字符串的索引器引用资源并将其应用于元素。 但是,在 C# 中使用 ResourceDictionary
没有什么优势,因为共享对象可以存储为字段或属性,并且可以直接访问,而无需先从字典中检索它们。
在 XAML 中创建资源
每个 VisualElement
派生对象都有一个 Resources
属性,该属性是可以包含资源的 ResourceDictionary
。 同样, Application
派生对象具有一个 Resources
属性,该属性是可以包含资源的 ResourceDictionary
。
Xamarin.Forms 应用程序仅包含派生自 Application
的类,但经常使用派生自 VisualElement
的许多类,包括页面、布局和控件。 这些对象中的任何一个都可以将其 Resources
属性设置为包含资源的 ResourceDictionary
。 选择特定 ResourceDictionary
的放置位置会影响资源的使用位置:
- 附加到视图(例如
Button
或Label
)的ResourceDictionary
中的资源只能应用于该特定对象。 - 附加到布局(例如
ResourceDictionary
或StackLayout
)的Grid
中的资源可以应用于该布局及其所有子布局。 - 在页面级别定义的
ResourceDictionary
中的资源可以应用于该页面及其所有子级。 - 在应用程序级别定义的
ResourceDictionary
中的资源可以应用于整个应用程序。
除隐式样式外,资源字典中的每个资源都必须具有由 x:Key
特性定义的唯一字符串键。
以下 XAML 展示了 App.xaml 文件中以应用程序级别 ResourceDictionary
定义的资源:
<Application xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResourceDictionaryDemo.App">
<Application.Resources>
<Thickness x:Key="PageMargin">20</Thickness>
<!-- Colors -->
<Color x:Key="AppBackgroundColor">AliceBlue</Color>
<Color x:Key="NavigationBarColor">#1976D2</Color>
<Color x:Key="NavigationBarTextColor">White</Color>
<Color x:Key="NormalTextColor">Black</Color>
<!-- Implicit styles -->
<Style TargetType="{x:Type NavigationPage}">
<Setter Property="BarBackgroundColor"
Value="{StaticResource NavigationBarColor}" />
<Setter Property="BarTextColor"
Value="{StaticResource NavigationBarTextColor}" />
</Style>
<Style TargetType="{x:Type ContentPage}"
ApplyToDerivedTypes="True">
<Setter Property="BackgroundColor"
Value="{StaticResource AppBackgroundColor}" />
</Style>
</Application.Resources>
</Application>
在此示例中,资源字典定义了一个 Thickness
资源、多个 Color
资源和两个隐式 Style
资源。 有关 App
类的详细信息,请参阅Xamarin.Forms 应用类。
注意
将所有资源放置在显式 ResourceDictionary
标记之间也是有效的。 但是,从 Xamarin.Forms 3.0 开始,不再需要 ResourceDictionary
标记。 相反,系统会自动创建 ResourceDictionary
对象,并且可以直接在 Resources
属性元素标记之间插入资源。
在 XAML 中使用资源
每个资源都有一个使用 x:Key
特性指定的键,该键将成为其在 ResourceDictionary
中的字典键。 该键用于引用带有 StaticResource
或 DynamicResource
标记扩展的 ResourceDictionary
中的资源。
StaticResource
标记扩展类似于 DynamicResource
标记扩展,两者都使用字典键从资源字典引用值。 但是,当 StaticResource
标记扩展执行单个字典查找时,DynamicResource
标记扩展会保留指向字典键的链接。 因此,如果替换了与键关联的字典条目,则该更改将应用于视觉对象元素。 这样,就可以在应用程序中进行运行时资源更改。 有关标记扩展的详细信息,请参阅 XAML 标记扩展。
以下 XAML 示例演示了如何使用资源,并在 StackLayout
中定义了其他资源:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="ResourceDictionaryDemo.HomePage"
Title="Home Page">
<StackLayout Margin="{StaticResource PageMargin}">
<StackLayout.Resources>
<!-- Implicit style -->
<Style TargetType="Button">
<Setter Property="FontSize" Value="Medium" />
<Setter Property="BackgroundColor" Value="#1976D2" />
<Setter Property="TextColor" Value="White" />
<Setter Property="CornerRadius" Value="5" />
</Style>
</StackLayout.Resources>
<Label Text="This app demonstrates consuming resources that have been defined in resource dictionaries." />
<Button Text="Navigate"
Clicked="OnNavigateButtonClicked" />
</StackLayout>
</ContentPage>
在此示例中,ContentPage
对象使用在应用程序级别资源字典中定义的隐式样式。 StackLayout
对象使用在应用程序级别资源字典中定义的 PageMargin
资源,而 Button
对象使用在 StackLayout
资源字典中定义的隐式样式。 这会导致如以下屏幕截图中所示的外观:
重要
特定于单个页面的资源不应包含在应用程序级资源字典中,因为此类资源将在应用程序启动时而不是在页面需要时进行分析。 有关详细信息,请参阅减小应用程序资源字典大小。
资源查找行为
当使用 StaticResource
或 DynamicResource
标记扩展引用资源时,会发生以下查找过程:
- 在资源字典中检查请求的键是否存在,如果存在,检查设置属性的元素。 如果找到请求的键,则返回其值,并且终止查找过程。
- 如果未找到匹配项,则查找过程向上搜索可视化树,检查每个父元素的资源字典。 如果找到请求的键,则返回其值,并且终止查找过程。 否则,该过程将继续向上,直至到达根元素。
- 如果在根元素上找不到匹配项,则会检查应用程序级别资源字典。
- 如果仍然找不到匹配项,则会引发
XamlParseException
。
因此,当 XAML 分析程序遇到 StaticResource
或 DynamicResource
标记扩展时,它会使用找到的第一个匹配项,通过向上浏览可视化树来搜索匹配键。 如果此搜索在该页面结束,并且键仍未找到,则 XAML 分析程序将搜索附加到 App
对象的 ResourceDictionary
。 如果仍找不到该键,则会引发异常。
重写资源
当资源共享键时,在可视化树较低部分中定义的资源将优先于较高部分中定义的资源。 例如,在应用程序级别将 AppBackgroundColor
资源设置为 AliceBlue
,将被设置为 Teal
的页面级别 AppBackgroundColor
资源重写。 同样,页面级别的 AppBackgroundColor
资源将被控件级别的 AppBackgroundColor
资源重写。
独立资源字典
派生自 ResourceDictionary
的类也可以位于独立的 XAML 文件中。 然后可以在应用程序之间共享 XAML 文件。
若要创建此类文件,请将新的“内容视图”或“内容页面”项添加到项目中(但不要仅通过一个 C# 文件添加“内容视图”或“内容页面”)。 删除代码隐藏文件,并在 XAML 文件中将基类的名称从 ContentView
或 ContentPage
更改为 ResourceDictionary
。 此外,从文件的根标记中删除 x:Class
属性。
以下 XAML 示例显示了名为 MyResourceDictionary.xaml 的 ResourceDictionary
:
<ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml">
<DataTemplate x:Key="PersonDataTemplate">
<ViewCell>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*" />
<ColumnDefinition Width="0.2*" />
<ColumnDefinition Width="0.3*" />
</Grid.ColumnDefinitions>
<Label Text="{Binding Name}"
TextColor="{StaticResource NormalTextColor}"
FontAttributes="Bold" />
<Label Grid.Column="1"
Text="{Binding Age}"
TextColor="{StaticResource NormalTextColor}" />
<Label Grid.Column="2"
Text="{Binding Location}"
TextColor="{StaticResource NormalTextColor}"
HorizontalTextAlignment="End" />
</Grid>
</ViewCell>
</DataTemplate>
</ResourceDictionary>
在此示例中,ResourceDictionary
包含单个资源,该资源是 DataTemplate
类型的对象。 可以通过将 MyResourceDictionary.xaml 合并到其他资源字典进行使用。
默认情况下,当链接器行为设置为链接所有程序集时,链接器将从发布版本中删除独立的 XAML 文件。 若要确保独立 XAML 文件保留在发布版本中:
将自定义
Preserve
属性添加到包含独立 XAML 文件的程序集中。 有关详细信息,请参阅保留代码。在程序集级别设置
Preserve
属性:[assembly:Preserve(AllMembers = true)]
有关链接的详细信息,请参阅链接 Xamarin.iOS 应用和 Android 上的链接。
合并的资源字典
合并的资源字典将一个或多个 ResourceDictionary
对象合并到另一个 ResourceDictionary
中。
合并本地资源字典
可以通过创建 ResourceDictionary
对象,将其 Source
属性设置为包含资源的 XAML 文件的文件名,从而将本地 ResourceDictionary
文件与另一个 ResourceDictionary
合并:
<ContentPage ...>
<ContentPage.Resources>
<!-- Add more resources here -->
<ResourceDictionary Source="MyResourceDictionary.xaml" />
<!-- Add more resources here -->
</ContentPage.Resources>
...
</ContentPage>
此语法不会实例化 MyResourceDictionary
类。 而是会引用 XAML 文件。 因此,在设置 Source
属性时,不需要代码隐藏文件,并且可以从 MyResourceDictionary.xaml 文件的根标记中移除 x:Class
特性。
重要
只能从 XAML 设置 Source
属性。
从其他程序集合并资源字典
还可以通过将 ResourceDictionary
添加到 ResourceDictionary
的 MergedDictionaries
属性,将其与另一个 ResourceDictionary
合并。 此方法允许合并资源字典,而不管它们驻留在哪个进程集中。 合并来自外部程序集的资源字典需要 ResourceDictionary
将生成操作设置为 EmbeddedResource,拥有代码隐藏文件,并在文件的根标记中定义 x:Class
属性。
警告
ResourceDictionary
类还会定义一个 MergedWith
属性。 但是,此属性已被弃用,不应再使用。
以下代码示例显示要添加到页面级别 ResourceDictionary
的 MergedDictionaries
集合中的两个资源字典:
<ContentPage ...
xmlns:local="clr-namespace:ResourceDictionaryDemo"
xmlns:theme="clr-namespace:MyThemes;assembly=MyThemes">
<ContentPage.Resources>
<ResourceDictionary>
<!-- Add more resources here -->
<ResourceDictionary.MergedDictionaries>
<!-- Add more resource dictionaries here -->
<local:MyResourceDictionary />
<theme:LightTheme />
<!-- Add more resource dictionaries here -->
</ResourceDictionary.MergedDictionaries>
<!-- Add more resources here -->
</ResourceDictionary>
</ContentPage.Resources>
...
</ContentPage>
在此示例中,同一程序集中的资源字典以及外部程序集中的资源字典合并到页面级资源字典中。 此外,还可以在 MergedDictionaries
属性-元素标记中添加其他 ResourceDictionary
对象,并在这些标记之外添加其他资源。
重要
ResourceDictionary
中只能有一个 MergedDictionaries
属性-元素标记,但可以根据需要放入任意数量的 ResourceDictionary
对象。
当合并的 ResourceDictionary
资源共享相同的 x:Key
属性值时,Xamarin.Forms 使用以下资源优先级:
- 资源字典的本地资源。
- 通过
MergedDictionaries
集合合并的资源字典中包含的资源,它们在MergedDictionaries
属性中以倒序列出。
注意
如果应用程序包含多个大型资源字典,则搜索资源字典可能是一项计算密集型任务。 因此,为了避免不必要的搜索,应确保应用程序中的每个页面仅使用适用于页面的资源字典。