天天看點

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

更多事件觸發器

前一章關于動畫的章節展示了一個按鈕,它在點選時旋轉或縮放。 雖然大多數動畫示例都是為了制作有趣的示範而采取極端措施,但是按鈕用一點動畫來響應點選并不是不合理的。 這是EventTrigger的完美工作。

這是另一個TriggerAction派生物。 它與ScaleAction類似,但包括對ScaleTo的兩次調用,而不是一次,是以命名為ScaleUpAndDownAction:

namespace Xamarin.FormsBook.Toolkit
{
    public class ScaleUpAndDownAction : TriggerAction<VisualElement>
    {
        public ScaleUpAndDownAction()
        {
            Anchor = new Point(0.5, 0.5);
            Scale = 2;
            Length = 500;
        }
        public Point Anchor { set; get; }
        public double Scale { set; get; }
        public int Length { set; get; }
        protected override async void Invoke(VisualElement visual)
        {
            visual.AnchorX = Anchor.X;
            visual.AnchorY = Anchor.Y;
            await visual.ScaleTo(Scale, (uint)Length / 2, Easing.SinOut);
            await visual.ScaleTo(1, (uint)Length / 2, Easing.SinIn);
        }
    }
}           

該類對Easing函數進行寫死以保持代碼簡單。

ButtonGrowth程式定義了一個内部Style,它設定了三個Button屬性,并包含一個EventTrigger,它使用預設參數調用ScaleUpAndDownAction以響應Clicked事件:

<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="ButtonGrowth.ButtonGrowthPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Button">
                <Setter Property="HorizontalOptions" Value="Center" />
                <Setter Property="VerticalOptions" Value="CenterAndExpand" />
                <Setter Property="FontSize" Value="Large" />
                <Style.Triggers>
                    <EventTrigger Event="Clicked">
                        <toolkit:ScaleUpAndDownAction />
                    </EventTrigger>
                </Style.Triggers>
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout>
        <Button Text="Button #1" />
        <Button Text="Button #2" />
        <Button Text="Button #3" />
    </StackLayout>
</ContentPage>           

這是三個按鈕,因為它們的大小随着點選而增長:

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

是否有可能在這裡使用兩個ScaleAction執行個體而不是ScaleUpAndDownAction-一個執行個體将Button按比例縮放,另一個執行個體将其縮小? 不。我們隻處理一個事件 - Clicked事件 - 當事件被觸發時必須調用所有事件。 EventTrigger當然可以調用多個動作,但這些動作同時發生。 同時運作的兩個ScaleAction執行個體将互相競争。

但是,有一個解決方案。 這是一個DelayedScaleAction類,它派生自ScaleAction,但在ScaleTo調用之前包含一個Task.Delay調用:

namespace Xamarin.FormsBook.Toolkit
{
    public class DelayedScaleAction : ScaleAction
    {
        public DelayedScaleAction() : base()
        {
            // Set defaults.
            Delay = 0;
        }
        public int Delay { set; get; }
        async protected override void Invoke(VisualElement visual)
        {
            visual.AnchorX = Anchor.X;
            visual.AnchorY = Anchor.Y;
            await Task.Delay(Delay);
            await visual.ScaleTo(Scale, (uint)Length, Easing);
        }
    }
}           

您現在可以修改ButtonGrowth XAML檔案以包含由Clicked事件觸發的兩個DelayedScaleAction對象。 這些都是同時調用的,但第二個的Delay屬性設定為與第一個的Length屬性相同的值,是以第一個ScaleTo在第二個ScaleTo開始時結束:

<Style TargetType="Button">
    __
    <Style.Triggers>
        <EventTrigger Event="Clicked">
            <toolkit:DelayedScaleAction Scale="2"
                                        Length="250"
                                        Easing="SinOut" />
            <toolkit:DelayedScaleAction Delay="250"
                                        Scale="1"
                                        Length="250"
                                        Easing="SinIn" />
        </EventTrigger>
    </Style.Triggers>
</Style>           

DelayedScaleAction比ScaleUpAndDownAction更難使用,但它更靈活,您還可以定義名為DelayedTranslateAction和DelayedRotateAction的類來添加到混合中。

在上一章中,您看到了一個名為JiggleButton的Button衍生物,它在單擊Button時運作一個簡短的動畫。 這是一種動畫,您可以使用TriggerAction實作。 優點是您可以将它與普通的Button類一起使用,并可能将效果與特定類型的視圖和特定事件分開,以便可以使用它

與其他觀點和其他事件。

這是一個TriggerAction派生,它實作了與JiggleButton相同類型的動畫,但有三個屬性使其更靈活。 為了更清楚地将它與早期的代碼區分開來,這個類的名稱是ShiverAction:

namespace Xamarin.FormsBook.Toolkit
{
    public class ShiverAction : TriggerAction<VisualElement>
    {
        public ShiverAction()
        {
            Length = 1000;
            Angle = 15;
            Vibrations = 10;
        }
        public int Length { set; get; }
        public double Angle { set; get; }
        public int Vibrations { set; get; }
        protected override void Invoke(VisualElement visual)
        {
            visual.Rotation = 0;
            visual.AnchorX = 0.5;
            visual.AnchorY = 0.5;
            visual.RotateTo(Angle, (uint)Length,
                new Easing(t => Math.Sin(Math.PI * t) *
                                Math.Sin(Math.PI * 2 * Vibrations * t)));
        }
    }
}           

請注意,Invoke将目标可視元素的Rotation屬性初始化為零。 這是為了避免連續兩次按下按鈕時出現問題,并且在前一個動畫仍在運作時調用Invoke。

ShiverButtonDemo程式的XAML檔案定義了一個隐式Style,其中包含ShiverAction,其極值設定為三個屬性:

<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="ShiverButtonDemo.ShiverButtonDemoPage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Button">
                <Setter Property="HorizontalOptions" Value="Center" />
                <Setter Property="VerticalOptions" Value="CenterAndExpand" />
                <Setter Property="FontSize" Value="Large" />
                <Style.Triggers>
                    <EventTrigger Event="Clicked">
                        <toolkit:ShiverAction Length="3000"
                                              Angle="45"
                                              Vibrations="25" />
                    </EventTrigger>
                </Style.Triggers>
            </Style>
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout>
        <Button Text="Button #1" />
        <Button Text="Button #2" />
        <Button Text="Button #3" />
    </StackLayout>
</ContentPage>           

三個Button元素共享同一個ShiverAction執行個體,但每次調用Invoke方法都是針對特定的Button對象。每個按鈕發抖都獨立于其他按鈕。

但是,如果您想使用ShiverAction來響應元素上的Tapped事件而不是按鈕上的Clicked事件,例如,使某些内容或圖像振動幀,該怎麼辦? Tapped事件僅由TapGestureRecognizer定義,但您無法将EventTrigger附加到TapGestureRecognizer,因為TapGestureRecognizer沒有Triggers集合。您也不能将EventTrigger附加到View對象并指定Tapped事件。在View對象上找不到Tapped事件。

解決方案是使用行為,本章後面将對此進行說明。

也可以使用EventTrigger對象進行條目驗證。這是一個名為NumericValidationAction的TriggerAction派生,其泛型參數為Entry,是以它僅适用于Entry視圖。調用Invoke時,參數是Entry對象,是以它可以通路特定于Entry的屬性,在本例中為Text和TextColor。該方法檢查Entry的Text屬性是否可以解析為有效的double。如果沒有,文本将顯示為紅色以提醒使用者:

namespace Xamarin.FormsBook.Toolkit
{
    public class NumericValidationAction : 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;
        }
    }
}           

您可以将此代碼附加到帶有TextThanged事件的EventTrigger的條目,如TriggerEntryValidation程式中所示:

<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="TriggerEntryValidation.TriggerEntryValidationPage"
             Padding="50">
    <StackLayout>
        <Entry Placeholder="Enter a System.Double">
            <Entry.Triggers>
                <EventTrigger Event="TextChanged">
                    <toolkit:NumericValidationAction />
                </EventTrigger>
            </Entry.Triggers>
        </Entry>
    </StackLayout>
</ContentPage>           

每當文本更改時,都會調用NumericValidationAction的Invoke方法。

螢幕截圖顯示了iOS和Windows 10移動裝置的有效數字條目,但Android裝置中的數字無效:

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

不幸的是,這在通用Windows平台上不能正常工作:如果在條目中鍵入了無效的數字,則隻有當條目失去輸入焦點時,文本才會變為紅色。 但是,它在其他Windows運作時平台(Windows 8.1和Windows Phone 8.1)上運作良好。

繼續閱讀