2017/8/16

Xamarin.Forms 教學系列文(二十三.壹)XAML 觸發程式 - Trigger & EeventTrigger


學習目標
  • Trigger - 屬性觸發
  • EeventTrigger - 事件觸發

當初在 第七章 介紹 XAML 時,XAML 似乎就僅是個建構使用者介面的標籤語法而已,

但到了二十三章,Xamairn 想讓 XAML 做更多 後端能做 的事情,
Triggers Behaviors 就這樣誕生了

Triggers 可以在屬性條件成立時,同時更改其他的屬性值。
Behaviors 則是更開放性的用法,在 Visual Element 內加入更多客製化的功能。

不論是 Triggers 或是 Behavior,都可以寫在 Style 內,讓程式 重複使用

Triggers

Visual Element 定義了 Triggers 屬性 可做設定,

其類型為 IList<TriggerBase>,意思就是 同一個 Visual Element可以放入多個 ,甚至多種 Trigger

可放入的 Trigger 種類如下:
  1. Trigger - 當屬性符合條件時要觸發
  2. EventTrigger - 當事件觸發時要觸發
  3. DataTrigger - 當綁定的屬性值符合條件時要觸發
  4. MultiTrigger - Trigger 有多組條件要做判斷時
本小節會先介紹 Trigger 和 EventTrigger。

Trigger

講到現在,相信大家對 Trigger 還是霧薩薩的...

先下個簡單的定義:
Trigger 就是一組設定好條件成立會去執行某事情 的 XAML 標籤
來看範例:
要做的事很簡單,當我們點擊 Entry 時,Entry 會變大...

更精準地說,
IsFocused 為 True 時,將 Scale 的值改為 1.5
當 IsFocused 變回 False 時,Scale 改回原本預設的值 1
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="EntryPop.EntryPopPage" 
             Padding="20, 50, 120, 0">
    
    <StackLayout Spacing="20">
        <Entry Placeholder="enter name" AnchorX="0">
            <!--設定 Triggers-->
            <Entry.Triggers>

                <!--設定條件,當 IsFocused 為 Value 時成立-->
                <Trigger TargetType="Entry" Property="IsFocused" Value="True">

                    <!--要執行的事情-->
                    <Setter Property="Scale" Value="1.5" />
                </Trigger>
            </Entry.Triggers>
        </Entry>
        <Entry Placeholder="enter address" AnchorX="0">
            <Entry.Triggers>
                <Trigger TargetType="Entry" Property="IsFocused" Value="True">
                    <Setter Property="Scale" Value="1.5" />
                </Trigger>
            </Entry.Triggers>
        </Entry>
        <Entry Placeholder="enter city and state" AnchorX="0">
            <Entry.Triggers>
                <Trigger TargetType="Entry" Property="IsFocused" Value="True">
                    <Setter Property="Scale" Value="1.5" />
                </Trigger>
            </Entry.Triggers>
        </Entry>
    </StackLayout>
</ContentPage>

執行結果:


Style 也定義了 Triggers 屬性,代表的是你可以把 Trigger 寫在 Style 內,讓其他物件共享此 Trigger。

讓我們改造一下上面程式碼,同時加入隱含 Style,簡化程式碼:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="StyledTriggers.StyledTriggersPage" 
             Padding="20, 50, 120, 0">
    
    <ContentPage.Resources>
        <ResourceDictionary>
            <Style TargetType="Entry">
                <Setter Property="AnchorX" Value="0" />
                <!--寫在 Style 內的 Trigger-->
                <Style.Triggers>
                    <Trigger TargetType="Entry" Property="IsFocused" Value="True">
                        <Setter Property="Scale" Value="1.5" />
                    </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>

如果今天,你不想讓 Entry 只是直接變大,而是慢慢的脹大,需要 Trigger 配合 Animation 的寫法… 
請繼續往下看...

EventTrigger

即使 Trigger 可以完全在 XAML 上執行,但有時也要寫一點 .cs 的…

例如我們想做的事情:在 Trigger 內搭配 Animation 使物件 慢慢脹大 或 慢慢縮小

但, XAML 並不支援寫 Animations,
所以,如果要用 Trigger 去觸發 Animation,勢必要寫在 .cs。

最常見的方法就是使用 EventTrigger,定義了兩個屬性:
  • Event of type string
  • Actions of type IList<TriggerAction>

EventTrigger 的定義就是:
當觸發 指定事件 時,會將所有在 Actions 內的 TriggerAction 加入執行
// TriggerAction 可以寫在 .cs

舉例來說,
你可以把 Focused 設定為 EventTrigger 指定的事件,
而當 Focused 事件 被觸發時,EventTrigger 底下的 TriggerAction 會被加入並執行。

而你的工作就是要 提供要執行的 TriggerAction 事件,覆寫 Invoke 方法

要提供的 TriggerAction 簡單來說可以長這樣:
    public class ScaleAction : TriggerAction< VisualElement>
    {
        protected override void Invoke(VisualElement visual)
        {
            visual.ScaleTo(1.5);
        }
    }

當然,你不會想把這支程式碼寫死。

上面簡單的 TriggerAction 在 Focused 事件內運作得很好,但同時你也會需要 Unfocused 事件,將大小恢復的 TriggerAction

來改造下 ScaleAtion,讓其更有彈性:
namespace Xamarin.FormsBook.Toolkit
{
    public class ScaleAction : TriggerAction< VisualElement>
    {
        public ScaleAction()
        {
            // 預設值. 
            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; }

       //Converter 附錄在本節最後. 
        [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);
        }
    }
}

注意到中間有一行 TypeConvert 搭配 Easing,Easing 是指物件消失時的動畫類型,
因為在 XAML 我們只能給予字串,.cs 是看不懂字串的,要自行轉換成 Easing 類別,轉換程式附錄在本節最後。

來看一下 XAML 怎用:
<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,指定觸發事件為 Focused-->
                    <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>


如果想要 屬性改變時 去執行 Actions ( 非指定事件觸發) 怎辦~?

讓我們回到一般的 Trigger,定義了兩個屬性,可以設定條件成立時要執行的 Actions:
  • EnterActions of type IList<TriggerAction>
  • ExitActions of type IList<TriggerAction>

來看範例:
當屬性 IsFocused 為 True 時,去執行 Actions
<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-->
                    <Trigger TargetType="Entry" Property="IsFocused" Value="True">

                        <!--設定 EnterActions-->
                        <Trigger.EnterActions>
                            <toolkit:ScaleAction Anchor="0, 0.5" 
                                                 Scale="1.5" 
                                                 Easing="SpringOut" />
                        </Trigger.EnterActions>
                        
                        <!--設定 ExitActions-->
                        <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>


附錄,TypeConverter(typeof(EasingConverter))
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));
        }
    }
}




2 則留言:

  1. 你好,有個 Xamarin 開發 App 的需求,是否方便與你聯繫? Paza / hipaza@gmail.com

    回覆刪除
    回覆
    1. 您好,可以來信我剛註冊好的信箱... loganedge.tw@gmail.com
      或是直接加我 LineID:Magic0800

      刪除