天天看點

第二十三章:觸發器和行為(三)

觸發動作和動畫

雖然某些觸發器可以完全在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對象。

繼續閱讀