使用样式创建一致的 UI

已完成

使用资源可有效避免 XAML 标记中的硬编码重复值,但它们应用起来可能很繁琐。 单独为每个属性赋值可能会使 XAML 杂乱而冗长。 本单元演示如何将多个设置分组到一个样式中,这有助于整理代码并使代码更易于维护。

资源如何使 XAML 变得杂乱

资源为一个属性提供一个值。 但是,使用大量资源可能会导致 XAML 变得冗长。 假设你想为按钮提供一个自定义外观。 首先要为所需的值创建资源。 然后,将每个资源应用于所有按钮。 以下代码显示了 XAML 标记如何查找两个按钮。

<Button
    Text = "OK"
    BackgroundColor = "{StaticResource highlightColor}"
    BorderColor = "{StaticResource borderColor}"
    BorderWidth = "{StaticResource borderWidth}"
    TextColor = "{StaticResource textColor}" />

<Button
    Text = "Cancel"
    BackgroundColor = "{StaticResource highlightColor}"
    BorderColor = "{StaticResource borderColor}"
    BorderWidth = "{StaticResource borderWidth}"
    TextColor = "{StaticResource textColor}" />

注意,每个按钮上都设置了相同的五个属性。 通过对资源的使用,消除了对其中四个中重复的硬编码值的需求。 但这种类型的 XAML 标记很快就会变得难以阅读。 此外,如果为每个控件设置了大量属性,那么很容易意外遗漏其中一个属性,从而导致控件外观不一致。 解决方法是创建一个可以一次性为四个属性赋值的样式。

什么是资源库?

资源库是用于创建样式的重要组件

资源库是“属性/值”对的容器。 资源库可以被视为表示一种赋值语句。 可指定要赋值的属性和要应用的值。 通常在 XAML 标记中创建资源库对象。 以下示例为 TextColor 属性创建资源库对象

<Setter Property="TextColor" Value="White" />

可使用资源作为资源库中的值,如以下代码所示。 如果想在多个资源库中使用相同的值,则该方法会非常有用。

<Setter Property="TextColor" Value="{StaticResource textColor}" />

注意

资源库中指定的属性值必须实现为可绑定属性。 在 .NET MAUI 中,以后缀 Property 结尾的控件上的所有属性都是可绑定属性。 如果尝试在资源库中使用 TextColor 这样的属性,请确保该控件中具有名为 TextColorProperty 的相应的可绑定属性。 实际上,几乎所有要在资源库中使用的属性都是以这种方式实现的。

什么是样式?

样式是一个面向特定类型控件的资源库集合。 .NET MAUI 需要目标类型,这样才可确保资源库中的属性存在于该类型中。

以下代码显示了一个样式,它合并了前一个示例中的四个值。 请注意,TargetType 设置为 Button,并且资源库中的所有属性都是 Button 类的成员。 不可将此样式用于标签,因为 Label 类不包含 BorderColor 或 BorderWidth 属性

<Style TargetType="Button">
    <Setter Property="BackgroundColor" Value="#2A84D3" />
    <Setter Property="BorderColor" Value="#1C5F9B" />
    <Setter Property="BorderWidth" Value="3" />
    <Setter Property="TextColor" Value="White" />
</Style>

定义样式

通常可在 ResourceDictionary 中将样式定义为资源。 借助资源字典,可轻松将样式应用于同一页面甚至整个应用程序中的多个控件。 以下代码演示了如何在字典中将样式定义为资源。 请注意,样式是使用 x:Key 属性命名的。 命名样式后,可以在 XAML 页面中引用该样式。

<ContentPage.Resources>
    <Style x:Key="MyButtonStyle" TargetType="Button">
        ...
    </Style>
</ContentPage.Resources>

应用样式

向控件附加样式的方法是将名称分配给 Style 属性。 该分配会使样式中的每个资源库对象都应用于目标控件。 以下代码演示如何将按钮样式应用于两个按钮。

<Button Text="OK" Style="{StaticResource MyButtonStyle}" />
<Button Text="Cancel" Style="{StaticResource MyButtonStyle}" />

在上一示例中,你使用了 StaticResource 标记扩展将样式附加到控件。 如果无需在运行时更改样式,此方法将非常有用。 但如果想要实现动态主题等内容,而 UI 需要更改,该怎么办? 在这种情况下,可使用 DynamicResource 标记扩展加载样式

<Button Text="Cancel" Style="{DynamicResource MyButtonStyle}" />

DynamicResource 侦听资源字典中的 x:Key 属性是否有替换。 如果编写的代码使用相同的 x:Key 值将新样式加载到 ResourceDictionary 中,则新样式将自动应用于 UI

对多个控件使用隐式样式

假设 UI 有 50 个按钮,你希望所有这些按钮都应用相同的样式。 据我们目前所知,对于每个按钮,都需要手动给 Style 属性赋值。 这并不难,但仍然很繁琐。

隐式样式是添加到资源字典而不用提供 x:Key 标识符的样式。 隐式样式会自动应用于指定 TargetType 对象的所有控件

下面的代码显示了声明为隐式样式的上一示例。 此样式将应用于页面上的每个按钮。

<ContentPage.Resources>
    <Style TargetType="Button">
        <Setter Property="BackgroundColor" Value="Blue" />
        <Setter Property="BorderColor" Value="Navy" />
        ...
    </Style>
</ContentPage.Resources>

重要

隐式样式与控件匹配时需要与指定 TargetType 完全匹配。 继承自目标类型的控件将不接收样式。 要影响继承的控件,可在定义样式时将 Style.ApplyToDerivedTypes 属性设置为 True。 例如,要将样式应用于 Button 类型并使其影响从 Button(如 ImageButton、RadioButton 或你创建的自定义类型)继承的任何按钮,可使用此类样式

<ContentPage.Resources>
    <Style TargetType="Button"
           ApplyToDerivedTypes="True">
        <Setter Property="BackgroundColor" Value="Black" />
    </Style>
</ContentPage.Resources>

替代样式

可将样式视为向控件提供一组默认值。 现有样式可能接近你的要求,但包含一两个不需要的资源库。 在这种情况下,可以通过直接设置属性来应用样式,然后重写值。 显式设置在样式之后应用,因此它将替代样式的值。

假设要对页面上的多个按钮使用以下样式。

<Style x:Key="MyButtonStyle" TargetType="Button">
    <Setter Property="BackgroundColor" Value="Blue" />
    <Setter Property="BorderRadius" Value="10" />
    <Setter Property="BorderWidth" Value="3" />
</Style>

此样式适用于所有按钮,需要红色背景的“取消”按钮除外。 只要还直接设置了 BackgroundColor 属性,就可以将相同样式用于“取消”按钮。 以下代码演示了如何替代颜色设置。

<Button
    Text="Cancel"
    Style="{StaticResource MyButtonStyle}"
    BackgroundColor="Red"
    ... />

面向上级类型

假设按钮和标签需要自定义背景色。 可以为每种类型创建单独的样式,也可以创建一个 TargetType 设置为 VisualElement 的样式。 这种方法之所以可行,是因为 VisualElement 是 Button 和 Label 的基类

以下代码展示了这样一种样式,它面向的是应用于两种不同派生类型的基类。

<Style x:Key="MyVisualElementStyle" TargetType="VisualElement">
    <Setter Property="BackgroundColor" Value="#2A84D3" />
</Style>
...
<Button Style="{StaticResource MyVisualElementStyle}" ... />
<Label Style="{StaticResource MyVisualElementStyle}" ... />

此示例使用 x:Key 标识样式,并且控件显式应用该样式。 隐式样式在此处不起作用,因为隐式样式的 TargetType 必须与控件类型完全匹配

使用 BasedOn 从样式继承

假设想为 UI 创建一致的外观。 你决定让所有控件都使用一致的背景色。 背景色设置可能会出现在多个样式中。 以下代码演示了两种带有重复资源库的样式。

<Style x:Key="MyButtonStyle" TargetType="Button">
    <Setter Property="BackgroundColor" Value="Blue" />
    <Setter Property="BorderColor" Value="Navy" />
    <Setter Property="BorderWidth" Value="5" />
</Style>

<Style x:Key="MyEntryStyle" TargetType="Entry">
    <Setter Property="BackgroundColor" Value="Blue" />
    <Setter Property="TextColor" Value="White" />
</Style>

可使用样式继承将重复的资源库提取到基准样式中。 要创建派生样式,可设置其 BasedOn 属性以引用基准样式。 新样式从其基准样式继承所有资源库。 派生样式还可以添加新的资源库或将继承的资源库替换为包含不同值的资源库。

下面的代码演示了之前的示例样式,它被重构为层次结构。 常见的资源库只出现在基准样式中而不是重复出现。 注意,请使用 StaticResource 标记来查找基准样式。 在这种情况下不能使用 DynamicResource

<Style x:Key="MyVisualElementStyle" TargetType="VisualElement">
    <Setter Property="BackgroundColor" Value="Blue" />
</Style>

<Style x:Key="MyButtonStyle" TargetType="Button" BasedOn="{StaticResource MyVisualElementStyle}">
    <Setter Property="BorderColor" Value="Navy" />
    <Setter Property="BorderWidth" Value="5" />
</Style>

<Style x:Key="MyEntryStyle" TargetType="Entry" BasedOn="{StaticResource MyVisualElementStyle}">
    <Setter Property="TextColor" Value="White" />
</Style>

基准样式和派生样式的 TargetType 值必须兼容。 要使样式兼容,它们必须具有相同的 TargetType 属性,或者派生样式的 TargetType 是基准样式的 TargetType 的后代

知识检查

1.

为什么要在 ResourceDictionary 中定义样式?