2017/3/24

Xamarin.Forms 教學系列文(十.貳)擴充標籤 - 動態 & 客製化



學習目標
  • DynamicResource
  • ResourceDictionary 的階層制
  • 客製化擴充標籤

延續上一小節的 Resource,除了 StaticResource 外,還有一個 DynamicResource,顧名思義就是要顯示動態資訊的資源檔,而最常見的動態資料就是時間

直接來看程式,我們先在 C# 設定好 Resource,其值每秒都會更改:
        public partial class DynamicVsStaticPage : ContentPage
        {
            public DynamicVsStaticPage()
            {
                InitializeComponent();

                Device.StartTimer(TimeSpan.FromSeconds(1), () =>
                {
                    Resources["currentDateTime"] = DateTime.Now.ToString();
                    return true;
                });
                
            }
        }


接著 XAML 內分別對兩個 Label 使用 StaticResource DyanaicResource
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="DynamicVsStatic.DynamicVsStaticPage" 
             Padding="5, 0">

    <!--Resources-->
    <ContentPage.Resources>
        <ResourceDictionary>
            <x:String x:Key="currentDateTime">Not actually a DateTime</x:String>
        </ResourceDictionary>
    </ContentPage.Resources>
    
    <StackLayout>
        <Label Text="StaticResource on Label.Text:" 
           VerticalOptions="EndAndExpand" FontSize="Medium" />

        <!--StaticResource-->
        <Label Text="{StaticResource currentDateTime}" 
           VerticalOptions="StartAndExpand" 
           HorizontalTextAlignment="Center"
           FontSize="Medium" />

        <Label Text="DynamicResource on Label.Text:" 
           VerticalOptions="EndAndExpand" 
           FontSize="Medium" />

        <!--DynamicResource-->
        <Label Text="{DynamicResource currentDateTime}" 
           VerticalOptions="StartAndExpand" 
           HorizontalTextAlignment="Center"
           FontSize="Medium" />

    </StackLayout>
    
</ContentPage>


再看結果就會發現差異了:
第二個用 DynamicResource 的  Label 會持續顯示當時的時間,第一個 StaticResource 則維持 XAML 給予的預設值。


Dictionaries 的階層

這邊有個很重要的觀念,設定 Dictionary 時,key 都是不能重複的。

當然 ResourceDictionary 也是 Dictionary 的一種,key 一樣不能重複。

但 !

 XAML 允許一種例外,當 ResourceDictionary 在不同階層時能出現重複的 key。

舉例來說,
這支程式碼內有兩組 ResourceDictionary,一組在 ContentPage,另一組在 StackLayout,都具有相同的 keytextColor

加入三顆 Button 來觀察資源檔的階層關係。
*OnPlatform 寫法已更改,請參考此篇文章
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
                 x:Class="ResourceTrees.ResourceTreesPage">

    <!--ContentPage Resources-->
    <ContentPage.Resources>
        <ResourceDictionary>
            <LayoutOptions x:Key="horzOptions">Center</LayoutOptions>
            <LayoutOptions x:Key="vertOptions" 
                           Alignment="Center" 
                           Expands="True" />
            
            <!--x:Key textColor-->
            <OnPlatform x:Key="textColor" 
                        x:TypeArguments="Color" 
                        iOS="Red" 
                        Android="Pink"
                        WinPhone="Blue" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <StackLayout>
        <Button Text=" Carpe diem " 
                HorizontalOptions="{StaticResource horzOptions}" 
                VerticalOptions="{StaticResource vertOptions}" 
                BorderWidth="{StaticResource borderWidth}" 
                TextColor="{StaticResource textColor}"
                BackgroundColor="{StaticResource backgroundColor}" 
                BorderColor="{StaticResource borderColor}"
                FontSize="{StaticResource fontSize}" />
        <StackLayout>

            <!--StackLayout Resources-->
            <StackLayout.Resources>
                <ResourceDictionary>

                    <!--x:Key textColor-->
                    <Color x:Key="textColor">Default</Color>
                    <x:String x:Key="fontSize">Default</x:String>
                </ResourceDictionary>
            </StackLayout.Resources>


            <Button Text=" Sapere aude " 
                  HorizontalOptions="{StaticResource horzOptions}" 
                  BorderWidth="{StaticResource borderWidth}" 
                  TextColor="{StaticResource textColor}"
                  BackgroundColor="{StaticResource backgroundColor}" 
                  BorderColor="{StaticResource borderColor}" 
                  FontSize="{StaticResource fontSize}" />

        </StackLayout>

        <Button Text=" Discere faciendo " 
                HorizontalOptions="{StaticResource horzOptions}"
                VerticalOptions="{StaticResource vertOptions}" 
                BorderWidth="{StaticResource borderWidth}" 
                TextColor="{StaticResource textColor}" 
                BackgroundColor="{StaticResource backgroundColor}"
                BorderColor="{StaticResource borderColor}" 
                FontSize="{StaticResource fontSize}" />

    </StackLayout>
</ContentPage>



執行結果,可以發現中間的按鈕採用的是 StackeLayout 內的 ResourceDictionary。


元件會選擇引用離自己最近的資源檔。

若是想寫整個 App 都使用的全域 ResourceDictinary,我們可以將資源檔放在最上層的 App class 或是 App.xaml 內。



客製化 markup extension

最後這小節教你如何製作屬於自己的擴充標籤,底下範例製作一個 HslColor 的標籤:
using System;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Xamarin.FormsBook.Toolkit
{
    public class HslColorExtension : IMarkupExtension
    {
        public HslColorExtension() { A = 1; }

        public double H { set; get; }
        public double S { set; get; }
        public double L { set; get; }
        public double A { set; get; }

        public object ProvideValue(IServiceProvider serviceProvider)
        {
            return Color.FromHsla(H, S, L, A);
        }
    }
}


要注意類別的 命名方式 和 繼承了 IMarkupExtension 這介面,一定要實作 ProvideValue 這方法並回傳值。

接著確保 PCL 的引用,先在 App 內初始化:
namespace CustomExtensionDemo
{
    public class App : Application
    {
        public App()
        {
            Xamarin.FormsBook.Toolkit.Toolkit.Init();
            MainPage = new CustomExtensionDemoPage();
        } 
        … 
    }
}


接著來看 XAML 如何使用,同樣注意最上方 toolkit 的引用:
<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="CustomExtensionDemo.CustomExtensionDemoPage">

    <StackLayout>
        <!-- Red -->
        <BoxView HorizontalOptions="Center" VerticalOptions="CenterAndExpand">
            <BoxView.Color>
                <toolkit:HslColorExtension H="0" S="1" L="0.5" />
            </BoxView.Color>
        </BoxView>

        <!-- Green -->
        <BoxView HorizontalOptions="Center" VerticalOptions="CenterAndExpand">
            <BoxView.Color>
                <toolkit:HslColorExtension H="0.33" S="1" L="0.5" />
            </BoxView.Color>
        </BoxView>

        <!-- Blue -->
        <BoxView Color="{toolkit:HslColor H=0.67, S=1, L=0.5}"
                 HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
        <!-- Gray -->
        <BoxView Color="{toolkit:HslColor H=0, S=0, L=0.5}" 
                 HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
        <!-- Semitransparent white -->
        <BoxView Color="{toolkit:HslColor H=0, S=0, L=1, A=0.5}"
                 HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
        <!-- Semitransparent black -->
        <BoxView Color="{toolkit:HslColor H=0, S=0, L=0, A=0.5}"
                 HorizontalOptions="Center" VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

執行結果:




5 則留言:

  1. 請問,
    這一行Xamarin.FormsBook.Toolkit.Toolkit.Init();
    會說找不到 FromsBook 這個 namespace,不過檔案我有照原文書建了,這是什麼問題?
    謝謝

    回覆刪除
    回覆
    1. 沒看到你的程式碼我也很難判定,如果確定
      namespace Xamarin.FormsBook.Toolkit
      {
      public class HslColorExtension : IMarkupExtension
      {
      ...
      都沒問題也存在於專案內,那試著把專案清除重建看看

      刪除
    2. 試出來了,專案的參考沒有把Xamarin.FormsBook.Toolkit加進來,所以不能用
      感謝

      刪除
  2. 羅根大大
    我想請問
    public object ProvideValue(IServiceProvider serviceProvider)
    {
    return Color.FromHsla(H, S, L, A);
    }

    在何時會被執行,是在xaml帶入參數後嗎??
    是因為IServiceProvider serviceProvider的關係嗎??
    感謝

    回覆刪除
    回覆
    1. 對喔,
      以 Xamarin 的角度來說,會先把 XAML 轉成 C#,
      所以擴充標籤也只是逐步去執行先寫好的副程式而已

      刪除