Xamarin.Forms 触发器使用详解

Viewed 0

触发器允许你在 XAML 中以声明的方式根据事件或属性更改来调整控件外观。状态触发器是一组专门的触发器,用于定义何时应用 VisualState

触发器可以直接分配给控件,或添加到页面级或应用级的资源字典中,以便应用到多个控件。

属性触发器

简单的属性触发器完全可以在 XAML 中表达。通过向控件的触发器集合添加 Trigger 元素来实现。以下示例展示了当 Entry 获得焦点时改变其背景颜色的触发器:

<Entry Placeholder="enter name">
    <Entry.Triggers>
        <Trigger TargetType="Entry"
                 Property="IsFocused" Value="True">
            <Setter Property="BackgroundColor" Value="Yellow" />
            <!-- 允许多个 Setter 元素 -->
        </Trigger>
    </Entry.Triggers>
</Entry>

触发器声明包含以下关键部分:

  • TargetType - 触发器适用的控件类型。
  • Property - 要监视的控件属性。
  • Value - 当监视的属性达到此值时,触发器激活。
  • Setter - Setter 元素集合,用于在触发条件满足时设置属性。必须指定 PropertyValue
  • EnterActions 和 ExitActions(未显示)- 这些可以在代码中编写,用于在 Setter 之外执行操作,下文将详细介绍。

使用样式应用触发器

触发器也可以添加到控件、页面或应用程序资源字典的 Style 声明中。以下示例声明了一个隐式样式(即未设置 Key),该样式应用于页面上的所有 Entry 控件:

<ContentPage.Resources>
    <ResourceDictionary>
        <Style TargetType="Entry">
            <Style.Triggers>
                <Trigger TargetType="Entry"
                         Property="IsFocused" Value="True">
                    <Setter Property="BackgroundColor" Value="Yellow" />
                    <!-- 允许多个 Setter 元素 -->
                </Trigger>
            </Style.Triggers>
        </Style>
    </ResourceDictionary>
</ContentPage.Resources>

数据触发器

数据触发器使用数据绑定来监视另一个控件,以触发 Setter。它使用 Binding 特性而不是 Property 特性来监视指定值。

以下示例使用数据绑定语法 {Binding Source={x:Reference entry}, Path=Text.Length} 来引用另一个控件的属性。当 entry 的文本长度为零时,触发器激活,从而禁用按钮:

<!-- 下面的 x:Name 在 DataTrigger 中被引用 -->
<!-- 提示:确保设置 Text=""(或其他默认值) -->
<Entry x:Name="entry"
       Text=""
       Placeholder="required field" />

<Button x:Name="button" Text="Save"
        FontSize="Large"
        HorizontalOptions="Center">
    <Button.Triggers>
        <DataTrigger TargetType="Button"
                     Binding="{Binding Source={x:Reference entry},
                                       Path=Text.Length}"
                     Value="0">
            <Setter Property="IsEnabled" Value="False" />
            <!-- 允许多个 Setter 元素 -->
        </DataTrigger>
    </Button.Triggers>
</Button>

提示:在评估 Path=Text.Length 时,始终为目标属性(如 Text="")提供默认值,否则可能为 null,导致触发器不按预期工作。

除了 Setter,还可以提供 EnterActionsExitActions

事件触发器

EventTrigger 元素只需 Event 属性,例如下面的 "Clicked"

<EventTrigger Event="Clicked">
    <local:NumericValidationTriggerAction />
</EventTrigger>

注意,这里没有 Setter 元素,而是引用了一个类 local:NumericValidationTriggerAction。这需要在页面的 XAML 中声明 xmlns:local

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:WorkingWithTriggers;assembly=WorkingWithTriggers">

类本身应实现 TriggerAction,并重写 Invoke 方法。每当触发事件发生时,就会调用该方法。

触发器操作实现应该:

  • 实现泛型 TriggerAction<T> 类,泛型参数对应于触发器将应用到的控件类型。可以使用 VisualElement 等超类来编写适用于多种控件的触发器操作,或指定如 Entry 的特定控件类型。
  • 重写 Invoke 方法,该方法在触发条件满足时调用。
  • 可选地公开可以在 XAML 中设置的属性。

示例类:

public class NumericValidationTriggerAction : TriggerAction<Entry>
{
    protected override void Invoke (Entry entry)
    {
        double result;
        bool isValid = Double.TryParse (entry.Text, out result);
        entry.TextColor = isValid ? Color.Default : Color.Red;
    }
}

在 XAML 中使用事件触发器:

<EventTrigger Event="TextChanged">
    <local:NumericValidationTriggerAction />
</EventTrigger>

注意:在共享 ResourceDictionary 中使用触发器时要小心,因为同一实例可能在控件之间共享,任何配置的状态都会应用到所有控件。事件触发器不支持 EnterActionsExitActions

多触发器

MultiTrigger 类似于 TriggerDataTrigger,但可以包含多个条件。所有条件必须为 true 才能触发 Setter

以下示例是一个绑定到两个不同输入(emailphone)的按钮触发器:

<MultiTrigger TargetType="Button">
    <MultiTrigger.Conditions>
        <BindingCondition Binding="{Binding Source={x:Reference email},
                                   Path=Text.Length}"
                               Value="0" />
        <BindingCondition Binding="{Binding Source={x:Reference phone},
                                   Path=Text.Length}"
                               Value="0" />
    </MultiTrigger.Conditions>
    <Setter Property="IsEnabled" Value="False" />
    <!-- 允许多个 Setter 元素 -->
</MultiTrigger>

Conditions 集合还可以包含 PropertyCondition 元素:

<PropertyCondition Property="Text" Value="OK" />

生成“全部需要”的多触发器

多触发器仅在满足所有条件时更新控件。测试“所有字段长度均为零”(例如所有输入必须完整的登录页)可能很棘手,因为需要条件“Text.Length > 0”,但这在 XAML 中无法直接表达。

可以使用 IValueConverter 来解决。以下转换器代码将 Text.Length 绑定转换为 bool,指示字段是否为空:

public class MultiTriggerConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        if ((int)value > 0) // length > 0 ?
            return true;    // 已输入数据
        else
            return false;   // 输入为空
    }

    public object ConvertBack(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        throw new NotSupportedException ();
    }
}

要在多触发器中使用此转换器,首先将其添加到页面的资源字典(以及自定义 xmlns:local 命名空间定义):

<ResourceDictionary>
   <local:MultiTriggerConverter x:Key="dataHasBeenEntered" />
</ResourceDictionary>

XAML 示例如下。注意与第一个触发器示例的差异:按钮默认设置为 IsEnabled="false";多触发器条件使用转换器将 Text.Length 值转换为布尔值;如果所有条件为 true,setter 将按钮的 IsEnabled 属性设置为 true

<Entry x:Name="user" Text="" Placeholder="user name" />
<Entry x:Name="pwd" Text="" Placeholder="password" />
<Button x:Name="loginButton" Text="Login"
        FontSize="Large"
        HorizontalOptions="Center"
        IsEnabled="false">
  <Button.Triggers>
    <MultiTrigger TargetType="Button">
      <MultiTrigger.Conditions>
        <BindingCondition Binding="{Binding Source={x:Reference user},
                              Path=Text.Length,
                              Converter={StaticResource dataHasBeenEntered}}"
                          Value="true" />
        <BindingCondition Binding="{Binding Source={x:Reference pwd},
                              Path=Text.Length,
                              Converter={StaticResource dataHasBeenEntered}}"
                          Value="true" />
      </MultiTrigger.Conditions>
      <Setter Property="IsEnabled" Value="True" />
    </MultiTrigger>
  </Button.Triggers>
</Button>

这些示例展示了多触发器的不同行为。在第一个多触发器示例中,只要一个 Entry 中有文本输入,就足以启用“保存”按钮。而在第二个示例中,“登录”按钮仅在两个字段都包含数据时才变为活动状态。

EnterActions 和 ExitActions

另一种在触发器发生时实现更改的方式是通过 EnterActionsExitActions 集合,并指定 TriggerAction<T> 实现。

EnterActions 集合定义在触发条件满足时调用的 TriggerAction 对象列表。ExitActions 集合定义在触发条件不再满足时调用的 TriggerAction 对象列表。

可以在触发器中同时提供 EnterActionsExitActionsSetter。但注意,Setter 会立即调用,而不会等待 EnterActionExitAction 完成。或者,可以完全在代码中执行操作,而不使用 Setter

示例:

<Entry Placeholder="enter job title">
    <Entry.Triggers>
        <Trigger TargetType="Entry"
                 Property="Entry.IsFocused" Value="True">
            <Trigger.EnterActions>
                <local:FadeTriggerAction StartsFrom="0" />
            </Trigger.EnterActions>
            <Trigger.ExitActions>
                <local:FadeTriggerAction StartsFrom="1" />
            </Trigger.ExitActions>
            <!-- 如果需要,可以同时使用 Enter/Exit 和 Setter -->
        </Trigger>
    </Entry.Triggers>
</Entry>

在 XAML 中引用类时,应声明命名空间,如 xmlns:local

FadeTriggerAction 类示例:

public class FadeTriggerAction : TriggerAction<VisualElement>
{
    public int StartsFrom { set; get; }

    protected override void Invoke(VisualElement sender)
    {
        sender.Animate("FadeTriggerAction", new Animation((d) =>
        {
            var val = StartsFrom == 1 ? d : 1 - d;
            sender.BackgroundColor = Color.FromRgb(1, val, 1);
        }),
        length: 1000, // 毫秒
        easing: Easing.Linear);
    }
}

状态触发器

状态触发器是一组专门的触发器,用于定义何时应用 VisualState。状态触发器添加到 VisualStateStateTriggers 集合中,该集合可以包含一个或多个触发器。当集合中的任何触发器处于活动状态时,就会应用 VisualState

使用状态触发器时,Xamarin.Forms 遵循以下优先规则来确定哪个触发器(及相应的 VisualState)处于活动状态:

  1. 任何派生自 StateTriggerBase 的触发器。
  2. 因满足 MinWindowWidth 条件而激活的 AdaptiveTrigger
  3. 因满足 MinWindowHeight 条件而激活的 AdaptiveTrigger

如果多个触发器同时处于活动状态,则标记中声明的第一个触发器优先。

注意:状态触发器可以在 Style 中设置,也可以直接对元素设置。

StateTrigger

StateTrigger 类派生自 StateTriggerBase,包含 IsActive 可绑定属性。当 IsActive 属性值更改时,StateTrigger 触发 VisualState 更改。

以下 XAML 示例展示了包含 StateTrigger 对象的 Style

<Style TargetType="Grid">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="Checked">
                    <VisualState.StateTriggers>
                        <StateTrigger IsActive="{Binding IsToggled}"
                                      IsActiveChanged="OnCheckedStateIsActiveChanged" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Black" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Unchecked">
                    <VisualState.StateTriggers>
                        <StateTrigger IsActive="{Binding IsToggled, Converter={StaticResource inverseBooleanConverter}}"
                                      IsActiveChanged="OnUncheckedStateIsActiveChanged" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="White" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

在此示例中,隐式 StyleGrid 对象为目标。当绑定对象的 IsToggled 属性为 true 时,Grid 的背景色设置为黑色;当变为 false 时,VisualState 更改触发,背景色变为白色。

此外,每当 VisualState 更改时,都会触发 IsActiveChanged 事件。可以为此事件注册处理程序以执行额外操作。

注意:可以通过从 StateTriggerBase 类派生并重写 OnAttachedOnDetached 方法来创建自定义状态触发器。

AdaptiveTrigger

当窗口达到指定高度或宽度时,AdaptiveTrigger 触发 VisualState 更改。它有两个可绑定属性:

  • MinWindowHeight:触发应用 VisualState 的最小窗口高度。
  • MinWindowWidth:触发应用 VisualState 的最小窗口宽度。

以下 XAML 示例展示了包含 AdaptiveTrigger 对象的 Style

<Style TargetType="StackLayout">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="Vertical">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="0" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="Orientation"
                                Value="Vertical" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Horizontal">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="800" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="Orientation"
                                Value="Horizontal" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

在此示例中,隐式 StyleStackLayout 对象为目标。当窗口宽度在 0 到 800 个设备无关单位之间时,StackLayout 采用垂直方向;当窗口宽度大于等于 800 个单位时,VisualState 更改触发,方向变为水平。

MinWindowHeightMinWindowWidth 属性可以单独使用或结合使用。

CompareStateTrigger

当属性等于特定值时,CompareStateTrigger 触发 VisualState 更改。它有两个可绑定属性:

  • Property:触发器所比较的属性。
  • Value:触发应用 VisualState 的值。

以下 XAML 示例展示了包含 CompareStateTrigger 对象的 Style

<Style TargetType="Grid">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="Checked">
                    <VisualState.StateTriggers>
                        <CompareStateTrigger Property="{Binding Source={x:Reference checkBox}, Path=IsChecked}"
                                             Value="True" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Black" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Unchecked">
                    <VisualState.StateTriggers>
                        <CompareStateTrigger Property="{Binding Source={x:Reference checkBox}, Path=IsChecked}"
                                             Value="False" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="White" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

在此示例中,隐式 StyleGrid 对象为目标。当 CheckBoxIsChecked 属性为 false 时,Grid 的背景色设置为白色;当变为 true 时,VisualState 更改触发,背景色变为黑色。

DeviceStateTrigger

DeviceStateTrigger 根据应用运行所在的设备平台触发 VisualState 更改。它有一个可绑定属性:

  • Device:触发应用 VisualState 的设备平台。

以下 XAML 示例展示了包含 DeviceStateTrigger 对象的 Style

<Style x:Key="DeviceStateTriggerPageStyle"
       TargetType="ContentPage">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="iOS">
                    <VisualState.StateTriggers>
                        <DeviceStateTrigger Device="iOS" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Silver" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Android">
                    <VisualState.StateTriggers>
                        <DeviceStateTrigger Device="Android" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="#2196F3" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="UWP">
                    <VisualState.StateTriggers>
                        <DeviceStateTrigger Device="UWP" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Aquamarine" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

在此示例中,显式 StyleContentPage 对象为目标。该样式根据设备平台设置不同的背景色:iOS 上为银色,Android 上为淡蓝色,UWP 上为海蓝色。

OrientationStateTrigger

当设备方向更改时,OrientationStateTrigger 触发 VisualState 更改。它有一个可绑定属性:

  • Orientation:触发应用 VisualState 的方向。

以下 XAML 示例展示了包含 OrientationStateTrigger 对象的 Style

<Style x:Key="OrientationStateTriggerPageStyle"
       TargetType="ContentPage">
    <Setter Property="VisualStateManager.VisualStateGroups">
        <VisualStateGroupList>
            <VisualStateGroup>
                <VisualState x:Name="Portrait">
                    <VisualState.StateTriggers>
                        <OrientationStateTrigger Orientation="Portrait" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="Silver" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="Landscape">
                    <VisualState.StateTriggers>
                        <OrientationStateTrigger Orientation="Landscape" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Property="BackgroundColor"
                                Value="White" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateGroupList>
    </Setter>
</Style>

在此示例中,显式 StyleContentPage 对象为目标。当设备方向为垂直时,背景色设置为银色;为水平时,设置为白色。

有关更多信息,请参考 Xamarin.Forms 可视状态管理器 和 Xamarin.Forms 触发器 API。

0 Answers