Xamarin.Forms 触发器
触发器允许你在根据事件或属性更改更改控件外观的 XAML 中以声明的方式表达操作。 此外,状态触发器是一组专门的触发器,定义了何时应该应用 VisualState
。
可以直接分配控件触发器,或将其添加到页面级别或应用级别的资源词典中,以应用到多个控件。
属性触发器
简单的触发器可以完全在 XAML 中表达,向控件的触发器集合添加 Trigger
元素。
此示例显示了收到焦点时更改 Entry
颜色的触发器:
<Entry Placeholder="enter name">
<Entry.Triggers>
<Trigger TargetType="Entry"
Property="IsFocused" Value="True">
<Setter Property="BackgroundColor" Value="Yellow" />
<!-- multiple Setters elements are allowed -->
</Trigger>
</Entry.Triggers>
</Entry>
触发器声明的重要部件有:
TargetType - 触发器适用的控件类型。
Property - 要监视的控件上的属性。
Value - 当针对监视的属性发生时,导致激活触发器的值。
Setter -
Setter
元素的集合,可进行添加且在满足触发条件时使用。 必须指定要设置的Property
和Value
。EnterActions 和 ExitActions (未显示) - 编写于代码中,且可用于
Setter
之外(或者不是该元素)的元素。 它们如下所述。
使用 style 应用触发器
还可将触发器添加到控件、页面或应用程序 ResourceDictionary
中的 Style
声明。 下面的示例声明隐式 style(即未设置 Key
);也就是说,它应用于页面上的所有 Entry
控件。
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Entry">
<Style.Triggers>
<Trigger TargetType="Entry"
Property="IsFocused" Value="True">
<Setter Property="BackgroundColor" Value="Yellow" />
<!-- multiple Setters elements are allowed -->
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
数据触发器
数据触发器使用数据绑定来监视另一个控件,以导致调用 Setter
。 设置 Binding
特性,而不是属性触发器中的 Property
特性,以监视指定值。
下面的示例使用数据绑定语法 {Binding Source={x:Reference entry}, Path=Text.Length}
这是我们引用另一个控件的属性的方式。 如果 entry
的长度为零,将激活触发器。 在此示例中,当输入为空时,触发器将禁用该按钮。
<!-- the x:Name is referenced below in DataTrigger-->
<!-- tip: make sure to set the Text="" (or some other default) -->
<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" />
<!-- multiple Setters elements are allowed -->
</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 中设置的属性。 相关示例,请参阅随附的示例应用程序中的
VisualElementPopTriggerAction
类。
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
,只是可能有多个条件。 触发 Setter
前,所有条件必须为 true。
下面的示例是绑定到两个不同输入(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" />
<!-- multiple Setter elements are allowed -->
</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; // some data has been entered
else
return false; // input is empty
}
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
值转换为boolean
。 - 如果所有条件为
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
对象的 IList
。 ExitActions
集合用于定义将在不再满足触发条件后调用的 TriggerAction
对象的 IList
。
注意
EventTrigger
类将忽略 EnterActions
和 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>
<!-- You can use both Enter/Exit and Setter together if required -->
</Trigger>
</Entry.Triggers>
</Entry>
如往常一样,如果在 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"
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;
// so i was aiming for a different color, but then i liked the pink :)
sender.BackgroundColor = Color.FromRgb(1, val, 1);
}),
length: 1000, // milliseconds
easing: Easing.Linear);
}
}
状态触发器
状态触发器是一组专门的触发器,它们定义了在哪些条件下应该应用 VisualState
。
状态触发器添加到 VisualState
的 StateTriggers
集合。 此集合可以包含一个或多个状态触发器。 当此集合中的任何状态触发器处于活动状态时,便会应用 VisualState
。
使用状态触发器来控制视觉对象状态时,Xamarin.Forms 使用以下优先规则来确定哪个触发器(以及相应的 VisualState
)处于活动状态:
- 任何派生自
StateTriggerBase
的触发器。 - 因满足
MinWindowWidth
条件而激活的AdaptiveTrigger
。 - 因满足
MinWindowHeight
条件而激活的AdaptiveTrigger
。
如果多个触发器同时处于活动状态(例如,两个自定义触发器),则标记中声明的第一个触发器优先。
注意
状态触发器可以在 Style
中设置,也可以直接对元素设置。
若要详细了解可视状态,请参阅 Xamarin.Forms 可视状态管理器。
状态触发器
StateTrigger
类派生自 StateTriggerBase
类,包含 IsActive
可绑定属性。 当 IsActive
属性值更改时,StateTrigger
触发 VisualState
更改。
StateTriggerBase
类是所有状态触发器的基类,包含 IsActive
属性和 IsActiveChanged
事件。 只要 VisualState
更改,就会触发此事件。 此外,StateTriggerBase
类包含可重写的 OnAttached
和 OnDetached
方法。
重要
StateTrigger.IsActive
可绑定属性隐藏继承的 StateTriggerBase.IsActive
属性。
下面的 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
的背景色设置为黑色。 当绑定对象的 IsToggled
属性变成 false
时,VisualState
更改触发,且 Grid
的背景色变成白色。
此外,只要 VisualState
更改,就会触发 VisualState
的 IsActiveChanged
事件。 每个 VisualState
都注册此事件的事件处理程序:
void OnCheckedStateIsActiveChanged(object sender, EventArgs e)
{
StateTriggerBase stateTrigger = sender as StateTriggerBase;
Console.WriteLine($"Checked state active: {stateTrigger.IsActive}");
}
void OnUncheckedStateIsActiveChanged(object sender, EventArgs e)
{
StateTriggerBase stateTrigger = sender as StateTriggerBase;
Console.WriteLine($"Unchecked state active: {stateTrigger.IsActive}");
}
在此示例中,当 IsActiveChanged
事件的处理程序触发时,处理程序输出消息来指明 VisualState
是否处于活动状态。 例如,当视觉对象状态从 Checked
更改为 Unchecked
时,以下消息输出到控制台窗口中:
Checked state active: False
Unchecked state active: True
注意
可以通过以下方法创建自定义状态触发器:从 StateTriggerBase
类派生,并重写 OnAttached
和 OnDetached
方法来执行任何所需注册和清理。
自适应触发器
当窗口为指定高度或宽度时,AdaptiveTrigger
触发 VisualState
更改。 此触发器有以下两个可绑定属性:
- 类型为
double
的MinWindowHeight
,指明触发应用VisualState
的最小窗口高度。 - 类型为
double
的MinWindowWidth
,指明触发应用VisualState
的最小窗口宽度。
注意
由于 AdaptiveTrigger
派生自 StateTriggerBase
类,因此可以将事件处理程序附加到 IsActiveChanged
事件。
下面的 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 个设备无关单位之间时,应用 Style
的 StackLayout
对象采用垂直方向。 当窗口宽度 >= 800 个设备无关单位时,VisualState
更改将被触发,且 StackLayout
方向更改为水平:
MinWindowHeight
和 MinWindowWidth
属性可以单独使用,也可以结合使用。 下面的 XAML 示例展示了如何设置这两个属性:
<AdaptiveTrigger MinWindowWidth="800"
MinWindowHeight="1200"/>
在此示例中,AdaptiveTrigger
指示当前窗口宽度为 >= 800 个与设备无关的单位,且当前窗口高度为 >= 1200 个与设备无关的单位时,将应用相应的 VisualState
。
比较状态触发器
当属性等于特定值时,CompareStateTrigger
触发 VisualState
更改。 此触发器有以下两个可绑定属性:
- 类型为
object
的Property
,指明触发器所比较的属性。 - 类型为
object
的Value
,指明触发应用VisualState
的值。
注意
由于 CompareStateTrigger
派生自 StateTriggerBase
类,因此可以将事件处理程序附加到 IsActiveChanged
事件。
下面的 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>
...
<Grid>
<Frame BackgroundColor="White"
CornerRadius="12"
Margin="24"
HorizontalOptions="Center"
VerticalOptions="Center">
<StackLayout Orientation="Horizontal">
<CheckBox x:Name="checkBox"
VerticalOptions="Center" />
<Label Text="Check the CheckBox to modify the Grid background color."
VerticalOptions="Center" />
</StackLayout>
</Frame>
</Grid>
在此示例中,隐式 Style
以 Grid
对象为目标。 当 CheckBox
的 IsChecked
属性为 false
时,Grid
的背景色设置为白色。 当 CheckBox.IsChecked
属性变成 true
时,VisualState
更改触发,且 Grid
的背景色变成黑色:
设备状态触发器
DeviceStateTrigger
根据应用运行所在的设备平台触发 VisualState
更改。 此触发器有以下一个可绑定属性:
- 类型为
string
的Device
,指明触发应用VisualState
的设备平台。
注意
由于 DeviceStateTrigger
派生自 StateTriggerBase
类,因此可以将事件处理程序附加到 IsActiveChanged
事件。
下面的 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
对象为目标。 ContentPage
对象使用的 style 将 iOS 上的背景色设置为银色,将 Android 上的背景色设置为淡蓝色,将 UWP 上的背景色设置为海蓝色。 下面的屏幕截图展示了 iOS 和 Android 上的页面效果:
方向状态触发器
当设备的方向更改时,OrientationStateTrigger
触发 VisualState
更改。 此触发器有以下一个可绑定属性:
- 类型为
DeviceOrientation
的Orientation
,指明触发应用VisualState
的方向。
注意
由于 OrientationStateTrigger
派生自 StateTriggerBase
类,因此可以将事件处理程序附加到 IsActiveChanged
事件。
下面的 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
对象为目标。 ContentPage
对象使用的 style 在方向为垂直时将背景色设置为银色,在方向为水平时将背景色设置为白色。