しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin × ResourceDictionary でプラットフォームごとに配色を変えたい

インフルエンザにかかってしまいまだ体調が本調子ではないですが、タイトルの通り DI コンテナではなく今度は ResourceDictionary で配色を切り替えてみたいと思います

ベースとなったのはこちらの記事

Application wide resources in Xamarin Forms « Corrado's Blog 2.0

上記をもとにまずは AppBase クラスを追加します

/// <summary>
/// アプリケーション基底クラス
/// </summary>
public class AppBase : View
{
}

Xamarin.Forms の View を継承している以外は特に中身なし!

次に App クラスを XAML を持つコードビハインドに改修します

/// <summary>
/// 共通 アプリケーションクラス
/// </summary>
public partial class App : AppBase
{
    /// <summary>
    /// アプリケーションクラス参照
    /// </summary>
    public static readonly App Current;

    /// <summary>
    /// アプリ内で管理するモジュールのコンテナ
    /// </summary>
    public static readonly UnityContainer Container = new UnityContainer();

    /// <summary>
    /// コンストラクタ
    /// </summary>
    static App()
    {
        Current = new App();
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    private App()
        : base()
    {
        this.InitializeComponent();

        // Prism.Mvvm.Xamarin アセンブリを読み込み可能にするおまじない
        var autoWired = ViewModelLocator.AutoWireViewModelProperty.DefaultValue;
    }

    /// <summary>
    /// アプリケーション起動処理
    /// </summary>
    public void OnLaunchApplication()
    {
        // 画面状態監視サービス を DI コンテナに登録
        Container.RegisterType<IPageStateDetectService, PageStateDetectService>(new ContainerControlledLifetimeManager());

        // ViewModel をインスタンス化するデフォルトメソッドを指定します
        ViewModelLocationProvider.SetDefaultViewModelFactory((type) => Container.Resolve(type));

        App.Container.RegisterType<NumberItemViewModel>(new InjectionProperty("Number", 0));
    }

    /// <summary>
    /// メイン画面を取得します
    /// </summary>
    /// <returns>メイン画面</returns>
    public static Page GetMainPage()
    {
        return new TopPage();
    }
}

AppBase を継承し、コンストラクタで InitializeComponent を呼ぶようにしてます

App.Container.RegisterType<NumberItemViewModel>(new InjectionProperty("Number", 0))

Unity で配色を設定しないので上記の部分で文字盤の初期値のみ設定しています

<?xml version="1.0" encoding="utf-8" ?>
<AppBase xmlns="http://xamarin.com/schemas/2014/forms"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="XamarinUnityInjection.App">
  <AppBase.Resources>
    <ResourceDictionary>
      <OnPlatform x:Key="HourHandColor"
                  x:TypeArguments="x:String"
                  iOS="#FFFF8469"
                  WinPhone="#FF08007F"/>
      <OnPlatform x:Key="MinuteHandColor"
                  x:TypeArguments="x:String"
                  iOS="#FFFF461C"
                  WinPhone="#FF0D00CC"/>
      <OnPlatform x:Key="SecondHandColor"
                  x:TypeArguments="x:String"
                  iOS="#FFCC3816"
                  WinPhone="#FF1000FF"/>
      <OnPlatform x:Key="TickColor"
                  x:TypeArguments="x:String"
                  iOS="#FF7F4234"
                  WinPhone="#FF2C267F"/>
      <OnPlatform x:Key="NumberColor"
                  x:TypeArguments="x:String"
                  iOS="#FF7F230E"
                  WinPhone="#FF584CFF"/>
    </ResourceDictionary>
  </AppBase.Resources>
</AppBase>

こちらは同 XAML 側で配色の設定をとりあえず OnPlatform で設定しています

さて、ここから本題

/// <summary>
/// AppResource のマークアップ拡張クラス
/// </summary>
[ContentProperty("Key")]
public class AppResourceExtension : IMarkupExtension
{
    /// <summary>
    /// リソースキー名
    /// </summary>
    public string Key { get; set; }

    /// <summary>
    /// 値を返却します
    /// </summary>
    /// <param name="serviceProvider">サービスプロバイダ</param>
    /// <returns></returns>
    public object ProvideValue(IServiceProvider serviceProvider)
    {
        if (this.Key == null)
        {
            throw new InvalidOperationException("you must specify a key in {AppResource}");
        }
        if (serviceProvider == null)
        {
            throw new ArgumentNullException("serviceProvider");
        }

        object value;
        bool found = App.Current.Resources.TryGetValue(this.Key, out value);
        if (found)
        {
            if (value is OnPlatform<string>)
            {
                var platform = (OnPlatform<string>)value;
                switch (Device.OS)
                {
                    case TargetPlatform.iOS:
                        return platform.iOS;

                    case TargetPlatform.Android:
                        return platform.Android;

                    case TargetPlatform.WinPhone:
                        return platform.WinPhone;
                }
            }
            return value;
        }
        throw new ArgumentNullException(string.Format("Can't find a application resource for key {0}", this.Key));
    }
}

App.xaml に定義した配色を参照するために AppResourceExtension というマークアップ拡張クラスを作ります なぜか OnPlatform をそのまま返すと配色に変換してくれないので途中で処理分岐しています(いずれなんとかしたい)

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:prismmvvm="clr-namespace:Prism.Mvvm;assembly=Prism.Mvvm.Xamarin"
             xmlns:local="clr-namespace:XamarinUnityInjection.Views;assembly=XamarinUnityInjection"
             xmlns:cv="clr-namespace:XamarinUnityInjection.Converters;assembly=XamarinUnityInjection"
             xmlns:vm="clr-namespace:XamarinUnityInjection.ViewModels;assembly=XamarinUnityInjection"
             prismmvvm:ViewModelLocator.AutoWireViewModel="true"
             local:PageStateDetect.DetectState="true"
             x:Class="XamarinUnityInjection.Views.TopPage">
  <ContentPage.Resources>
    <ResourceDictionary>
      <cv:RectangleConverter x:Key="RectangleConverter"/>
      <cv:ColorConverter x:Key="ColorConverter"/>
    </ResourceDictionary>
  </ContentPage.Resources>
  
    <AbsoluteLayout HorizontalOptions="FillAndExpand"
                    VerticalOptions="FillAndExpand">
      
      <!-- 目盛り描画 -->
      <local:AbsoluteItemsControl
        HorizontalOptions="FillAndExpand"
        VerticalOptions="FillAndExpand"
        x:TypeArguments="vm:TickItemViewModel"
        ItemsSource="{Binding TickItems}">
        <local:AbsoluteItemsControl.ItemTemplate>
          <DataTemplate>
            <BoxView Color="{local:AppResource TickColor}"
                     AbsoluteLayout.LayoutBounds="{Binding LayoutBounds, Converter={StaticResource RectangleConverter}}"
                     AbsoluteLayout.LayoutFlags="None"
                     AnchorX="0.5"
                     AnchorY="0.5"
                     Rotation="{Binding Rotation}"/>
          </DataTemplate>
        </local:AbsoluteItemsControl.ItemTemplate>
      </local:AbsoluteItemsControl>

      <!-- 数字描画 -->
      <local:AbsoluteItemsControl  HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand"
        x:TypeArguments="vm:NumberItemViewModel"
        ItemsSource="{Binding NumberItems}">
        <local:AbsoluteItemsControl.ItemTemplate>
          <DataTemplate>
            <Label TextColor="{local:AppResource NumberColor}"
                   HorizontalOptions="FillAndExpand"
                   VerticalOptions="CenterAndExpand"
                   Font="20"
                   Text="{Binding NumberString}"
                   AbsoluteLayout.LayoutBounds="{Binding LayoutBounds, Converter={StaticResource RectangleConverter}}"
                   AbsoluteLayout.LayoutFlags="None"
                   AnchorX="0.5"
                   AnchorY="0.5"
                   Rotation="{Binding Rotation}"/>
          </DataTemplate>
        </local:AbsoluteItemsControl.ItemTemplate>
      </local:AbsoluteItemsControl>

      <!-- 時針描画 -->
      <BoxView BindingContext="{Binding HourHand}"
               Color="{local:AppResource HourHandColor}"
               Opacity="0.5"
               AbsoluteLayout.LayoutBounds="{Binding LayoutBounds, Converter={StaticResource RectangleConverter}}"
               AbsoluteLayout.LayoutFlags="None"
               AnchorX="0.5"
               AnchorY="0.5"
               Rotation="{Binding Rotation}"/>
      
      <!-- 分針描画 -->
      <BoxView BindingContext="{Binding MinuteHand}"
               Color="{local:AppResource MinuteHandColor}"
               Opacity="0.5"
               AbsoluteLayout.LayoutBounds="{Binding LayoutBounds, Converter={StaticResource RectangleConverter}}"
               AbsoluteLayout.LayoutFlags="None"
               AnchorX="0.5"
               AnchorY="0.5"
               Rotation="{Binding Rotation}"/>

      <!-- 秒針描画 -->
      <BoxView BindingContext="{Binding SecondHand}"
               Color="{local:AppResource SecondHandColor}"
               Opacity="0.5"
               AbsoluteLayout.LayoutBounds="{Binding LayoutBounds, Converter={StaticResource RectangleConverter}}"
               AbsoluteLayout.LayoutFlags="None"
               AnchorX="0.5"
               AnchorY="0.5"
               Rotation="{Binding Rotation}"/>
    </AbsoluteLayout>
</ContentPage>

あとはトップ画面の XAML の Color のところを Binding から AppResource による指定に切り替えれば・・・

f:id:matatabi_ux:20141205080512p:plain

はい同じようにできましたね