Xamarin.Forms 资源字典

ResourceDictionary 是 Xamarin.Forms 应用程序使用的资源的存储库。 存储在 ResourceDictionary 中的典型资源包括样式控件模板数据模板、颜色和转换器。

在 XAML 中,可以使用 StaticResourceDynamicResource 标记扩展来引用存储在 ResourceDictionary 中的资源并将其应用于元素。 在 C# 中,还可以在 ResourceDictionary 中定义资源,然后使用基于字符串的索引器引用资源并将其应用于元素。 但是,在 C# 中使用 ResourceDictionary 没有什么优势,因为共享对象可以存储为字段或属性,并且可以直接访问,而无需先从字典中检索它们。

在 XAML 中创建资源

每个 VisualElement 派生对象都有一个 Resources 属性,该属性是可以包含资源的 ResourceDictionary。 同样, Application 派生对象具有一个 Resources 属性,该属性是可以包含资源的 ResourceDictionary

Xamarin.Forms 应用程序仅包含派生自 Application 的类,但经常使用派生自 VisualElement 的许多类,包括页面、布局和控件。 这些对象中的任何一个都可以将其 Resources 属性设置为包含资源的 ResourceDictionary。 选择特定 ResourceDictionary 的放置位置会影响资源的使用位置:

  • 附加到视图(例如 ButtonLabel)的 ResourceDictionary 中的资源只能应用于该特定对象。
  • 附加到布局(例如 ResourceDictionaryStackLayout)的 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 中的字典键。 该键用于引用带有 StaticResourceDynamicResource 标记扩展的 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 资源字典中定义的隐式样式。 这会导致如以下屏幕截图中所示的外观:

使用 ResourceDictionary 资源

重要

特定于单个页面的资源不应包含在应用程序级资源字典中,因为此类资源将在应用程序启动时而不是在页面需要时进行分析。 有关详细信息,请参阅减小应用程序资源字典大小

资源查找行为

当使用 StaticResourceDynamicResource 标记扩展引用资源时,会发生以下查找过程:

  • 在资源字典中检查请求的键是否存在,如果存在,检查设置属性的元素。 如果找到请求的键,则返回其值,并且终止查找过程。
  • 如果未找到匹配项,则查找过程向上搜索可视化树,检查每个父元素的资源字典。 如果找到请求的键,则返回其值,并且终止查找过程。 否则,该过程将继续向上,直至到达根元素。
  • 如果在根元素上找不到匹配项,则会检查应用程序级别资源字典。
  • 如果仍然找不到匹配项,则会引发 XamlParseException

因此,当 XAML 分析程序遇到 StaticResourceDynamicResource 标记扩展时,它会使用找到的第一个匹配项,通过向上浏览可视化树来搜索匹配键。 如果此搜索在该页面结束,并且键仍未找到,则 XAML 分析程序将搜索附加到 App 对象的 ResourceDictionary。 如果仍找不到该键,则会引发异常。

重写资源

当资源共享键时,在可视化树较低部分中定义的资源将优先于较高部分中定义的资源。 例如,在应用程序级别将 AppBackgroundColor 资源设置为 AliceBlue,将被设置为 Teal 的页面级别 AppBackgroundColor 资源重写。 同样,页面级别的 AppBackgroundColor 资源将被控件级别的 AppBackgroundColor 资源重写。

独立资源字典

派生自 ResourceDictionary 的类也可以位于独立的 XAML 文件中。 然后可以在应用程序之间共享 XAML 文件。

若要创建此类文件,请将新的“内容视图”或“内容页面”项添加到项目中(但不要仅通过一个 C# 文件添加“内容视图”或“内容页面”)。 删除代码隐藏文件,并在 XAML 文件中将基类的名称从 ContentViewContentPage 更改为 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 文件保留在发布版本中:

  1. 将自定义 Preserve 属性添加到包含独立 XAML 文件的程序集中。 有关详细信息,请参阅保留代码

  2. 在程序集级别设置 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 添加到 ResourceDictionaryMergedDictionaries 属性,将其与另一个 ResourceDictionary 合并。 此方法允许合并资源字典,而不管它们驻留在哪个进程集中。 合并来自外部程序集的资源字典需要 ResourceDictionary 将生成操作设置为 EmbeddedResource,拥有代码隐藏文件,并在文件的根标记中定义 x:Class 属性。

警告

ResourceDictionary 类还会定义一个 MergedWith 属性。 但是,此属性已被弃用,不应再使用。

以下代码示例显示要添加到页面级别 ResourceDictionaryMergedDictionaries 集合中的两个资源字典:

<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 使用以下资源优先级:

  1. 资源字典的本地资源。
  2. 通过 MergedDictionaries 集合合并的资源字典中包含的资源,它们在 MergedDictionaries 属性中以倒序列出。

注意

如果应用程序包含多个大型资源字典,则搜索资源字典可能是一项计算密集型任务。 因此,为了避免不必要的搜索,应确保应用程序中的每个页面仅使用适用于页面的资源字典。

第 9 频道YouTube 上查找更多 Xamarin 视频。