觸發動作和動畫
雖然某些觸發器可以完全在XAML中實現,但其他觸發器需要一些代碼支持。 如您所知,Xamarin.Forms沒有直接支持在XAML中實現動畫,因此如果您想使用觸發器爲元素設置動畫,則需要一些代碼。
有幾種方法可以從XAML調用動畫。 最明顯的方法是使用EventTrigger,它定義了兩個屬性:
- Event 類型是 string。
- Actions 類型是 IList。
當您附加觸發器的元素觸發該特定事件時,EventTrigger將調用Actions集合中的所有TriggerAction對象。
例如,VisualElement定義了與輸入焦點相關的兩個事件:聚焦和未聚焦。您可以將這些事件名稱設置爲兩個不同EventTrigger對象的Event屬性。當元素觸發事件時,將調用TriggerAction類型的對象。你的工作是提供一個派生自TriggerAction的類。此派生類重寫名爲Invoke的方法以響應事件。
Xamarin.Forms定義了TriggerAction類和TriggerAction 類,但這兩個類都是抽象的。通常,您將從TriggerAction 派生並將type參數設置爲觸發器操作可以支持的最通用的類。
例如,假設您要從TriggerAction 派生一個調用ScaleTo的類來爲Scale屬性設置動畫。將type參數設置爲VisualElement,因爲這是ScaleTo擴展方法引用的類。該類型的對象也傳遞給Invoke。
按照慣例,從TriggerAction派生的類在其名稱中有一個Action後綴。這樣的類可以這麼簡單:
public class ScaleAction : TriggerAction<VisualElement>
{
protected override void Invoke(VisualElement visual)
{
visual.ScaleTo(1.5);
}
}
當您將此類包含在附加到Entry視圖的EventTrigger中時,特定的Entry對象將作爲參數傳遞給Invoke方法,該方法使用ScaleTo爲該Entry對象設置動畫。 Entry在默認的四分之一秒內擴展到原始大小的150%。
當然,您可能不希望使該類具體。 對於Focused事件,這個簡單的ScaleAction類可以正常工作,但是對於Unfocused事件,您需要一個不同的類來將Scale屬性設置爲1。
您的Action 派生可以包含使類非常通用的屬性。 您甚至可以使ScaleAction類如此通用,使其基本上成爲ScaleTo方法的包裝器。 這是Xamarin.FormsBook.Toolkit庫中的ScaleAction版本:
namespace Xamarin.FormsBook.Toolkit
{
public class ScaleAction : TriggerAction<VisualElement>
{
public ScaleAction()
{
// Set defaults.
Anchor = new Point (0.5, 0.5);
Scale = 1;
Length = 250;
Easing = Easing.Linear;
}
public Point Anchor { set; get; }
public double Scale { set; get; }
public int Length { set; get; }
[TypeConverter(typeof(EasingConverter))]
public Easing Easing { set; get; }
protected override void Invoke(VisualElement visual)
{
visual.AnchorX = Anchor.X;
visual.AnchorY = Anchor.Y;
visual.ScaleTo(Scale, (uint)Length, Easing);
}
}
}
您可能想知道是否應該使用可綁定屬性來支持這些屬性,以便它們可以成爲數據綁定的目標。 但是,您可以這樣做,因爲TriggerAction派生自Object而不是BindableObject。 保持屬性簡單。
請注意Easing屬性上的TypeConverter屬性。 此Easing屬性可能在XAML中設置,但XAML解析器不知道如何將文本字符串轉換爲“輸入”和“輸出”類型的對象。 以下自定義類型轉換器(也在Xamarin.Forms Book.Toolkit中)幫助XAML解析器將文本字符串轉換爲Easing對象:
namespace Xamarin.FormsBook.Toolkit
{
public class EasingConverter : TypeConverter
{
public override bool CanConvertFrom(Type sourceType)
{
if (sourceType == null)
throw new ArgumentNullException("EasingConverter.CanConvertFrom: sourceType");
return (sourceType == typeof(string));
}
public override object ConvertFrom(CultureInfo culture, object value)
{
if (value == null || !(value is string))
return null;
string name = ((string)value).Trim();
if (name.StartsWith("Easing"))
{
name = name.Substring(7);
}
FieldInfo field = typeof(Easing).GetRuntimeField(name);
if (field != null && field.IsStatic)
{
return (Easing)field.GetValue(null);
}
throw new InvalidOperationException(
String.Format("Cannot convert \"{0}\" into Xamarin.Forms.Easing", value));
}
}
}
EntrySwell程序在其資源字典中定義了一個隱含的條目樣式。 該Style在其Triggers集合中有兩個EventTrigger對象,一個用於Focused,另一個用於Unfocused。 兩者都調用ScaleAction但具有不同的屬性設置:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="EntrySwell.EntrySwellPage"
Padding="20, 50, 120, 0">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Entry">
<Style.Triggers>
<EventTrigger Event="Focused">
<toolkit:ScaleAction Anchor="0, 0.5"
Scale="1.5"
Easing="SpringOut" />
</EventTrigger>
<EventTrigger Event="Unfocused">
<toolkit:ScaleAction Anchor="0, 0.5"
Scale="1" />
</EventTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Spacing="20">
<Entry Placeholder="enter name" />
<Entry Placeholder="enter address" />
<Entry Placeholder="enter city and state" />
</StackLayout>
</ContentPage>
請注意,EventTrigger不需要TargetType屬性。 EventTrigger定義的唯一構造函數沒有參數。
當每個條目獲得輸入焦點時,您會看到它變大,然後短暫地超過1.5 Scale值。 這就是SpringOut緩動函數的效果。
如果您想使用自定義緩動功能怎麼辦? 當然,您需要在代碼中定義這樣的緩動函數,並且可以在代碼隱藏文件中執行此操作。 但是你會怎麼引用呢?
XAML中的緩動功能? 這是如何做:
首先,從XAML文件中刪除ResourceDictionary標記。 這些標記實例化ResourceDictionary並將其設置爲Resources屬性。
其次,在代碼隱藏文件的構造函數中,實例化ResourceDictionary並將其設置爲Resources屬性。 在InitializeComponent之前執行此操作,以便在解析XAML文件時它存在:
Resources = new ResourceDictionary();
InitializeComponent();
第三,在這兩個語句之間,將一個帶有自定義緩動函數的Easing對象添加到Resources字典中:
Resources = new ResourceDictionary();
Resources.Add("customEase", new Easing(t => -6 * t * t + 7 * t));
InitializeComponent();
這個二次公式將0映射到0和1到1,但是0.5到2,因此很明顯動畫是否正確使用了這個緩動函數。
最後,在EventTrigger定義中引用使用StaticResource的字典條目:
<EventTrigger Event="Focused">
<toolkit:ScaleAction Anchor="0, 0.5"
Scale="1.5"
Easing="{StaticResource customEase}" />
</EventTrigger>
因爲Resources字典中的對象是Easing類型,所以XAML解析器將它直接分配給ScaleAction的Easing屬性並繞過TypeConverter。
本章的代碼示例中有一個名爲CustomEasingSwell的解決方案,它演示了這種技術。
不要使用DynamicResource將自定義Easing對象設置爲Easing屬性,可能希望稍後在代碼中定義緩動函數。 DynamicResource要求target屬性由可綁定屬性支持; StaticResource沒有。
您已經瞭解瞭如何使用Trigger設置屬性以響應屬性更改,而EventTrigger則調用TriggerAction對象以響應事件觸發。
但是如果你想調用TriggerAction以響應屬性更改呢? 也許您想從XAML調用動畫,但EventTrigger沒有適當的事件。
還有第二種方法可以調用涉及常規Trigger類而不是EventTrigger的TriggerAction派生。 如果查看TriggerBase(所有其他觸發器類派生的類)的文檔,您將看到以下兩個屬性:
- EnterActions 類型是 IList
- ExitActions 類型是 IList
與Trigger一起使用時,當Trigger條件變爲true時,將調用EnterActions集合中的所有TriggerAction對象,並在條件再次變爲false時調用ExitActions集合中的所有對象。
EnterExitSwell程序演示了這種技術。 它使用Trigger監視IsFocused屬性,並調用兩個ScaleAction實例,以在IsFocused變爲True時增加Entry的大小,並在IsFocused停止爲True時減小Entry的大小:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit=
"clr-namespace:Xamarin.FormsBook.Toolkit;assembly=Xamarin.FormsBook.Toolkit"
x:Class="EnterExitSwell.EnterExitSwellPage"
Padding="20, 50, 120, 0">
<ContentPage.Resources>
<ResourceDictionary>
<Style TargetType="Entry">
<Style.Triggers>
<Trigger TargetType="Entry" Property="IsFocused" Value="True">
<Trigger.EnterActions>
<toolkit:ScaleAction Anchor="0, 0.5"
Scale="1.5"
Easing="SpringOut" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<toolkit:ScaleAction Anchor="0, 0.5"
Scale="1" />
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout Spacing="20">
<Entry Placeholder="enter name" />
<Entry Placeholder="enter address" />
<Entry Placeholder="enter city and state" />
</StackLayout>
</ContentPage>
總之,您可以通過使用Trigger或使用EventTrigger觸發事件來調用從TriggerAction 派生的類。
但是不要在EventTrigger中使用EnterActions和ExitActions。 EventTrigger僅調用其Actions集合中的TriggerAction對象。