触发器允许你在 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元素集合,用于在触发条件满足时设置属性。必须指定Property和Value。 - 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,还可以提供 EnterActions 和 ExitActions。
事件触发器
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 中使用触发器时要小心,因为同一实例可能在控件之间共享,任何配置的状态都会应用到所有控件。事件触发器不支持 EnterActions 和 ExitActions。
多触发器
MultiTrigger 类似于 Trigger 或 DataTrigger,但可以包含多个条件。所有条件必须为 true 才能触发 Setter。
以下示例是一个绑定到两个不同输入(email 和 phone)的按钮触发器:
<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
另一种在触发器发生时实现更改的方式是通过 EnterActions 和 ExitActions 集合,并指定 TriggerAction<T> 实现。
EnterActions 集合定义在触发条件满足时调用的 TriggerAction 对象列表。ExitActions 集合定义在触发条件不再满足时调用的 TriggerAction 对象列表。
可以在触发器中同时提供 EnterActions、ExitActions 和 Setter。但注意,Setter 会立即调用,而不会等待 EnterAction 或 ExitAction 完成。或者,可以完全在代码中执行操作,而不使用 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。状态触发器添加到 VisualState 的 StateTriggers 集合中,该集合可以包含一个或多个触发器。当集合中的任何触发器处于活动状态时,就会应用 VisualState。
使用状态触发器时,Xamarin.Forms 遵循以下优先规则来确定哪个触发器(及相应的 VisualState)处于活动状态:
- 任何派生自
StateTriggerBase的触发器。 - 因满足
MinWindowWidth条件而激活的AdaptiveTrigger。 - 因满足
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>
在此示例中,隐式 Style 以 Grid 对象为目标。当绑定对象的 IsToggled 属性为 true 时,Grid 的背景色设置为黑色;当变为 false 时,VisualState 更改触发,背景色变为白色。
此外,每当 VisualState 更改时,都会触发 IsActiveChanged 事件。可以为此事件注册处理程序以执行额外操作。
注意:可以通过从 StateTriggerBase 类派生并重写 OnAttached 和 OnDetached 方法来创建自定义状态触发器。
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>
在此示例中,隐式 Style 以 StackLayout 对象为目标。当窗口宽度在 0 到 800 个设备无关单位之间时,StackLayout 采用垂直方向;当窗口宽度大于等于 800 个单位时,VisualState 更改触发,方向变为水平。
MinWindowHeight 和 MinWindowWidth 属性可以单独使用或结合使用。
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>
在此示例中,隐式 Style 以 Grid 对象为目标。当 CheckBox 的 IsChecked 属性为 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>
在此示例中,显式 Style 以 ContentPage 对象为目标。该样式根据设备平台设置不同的背景色: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>
在此示例中,显式 Style 以 ContentPage 对象为目标。当设备方向为垂直时,背景色设置为银色;为水平时,设置为白色。
有关更多信息,请参考 Xamarin.Forms 可视状态管理器 和 Xamarin.Forms 触发器 API。