Unity のプロパティ注入だけでプラットフォームごとに Xamarin アプリの配色を切り替える
Xamarin でプラットフォームごとに配色を切り替える場合、一般には下記のような XAML を記述すると思います
<OnPlatform x:Key="SecondHandColor" x:TypeArguments="Color" iOS="Red" Android="Aqua" WinPhone="#80FF80" />
ただ、こうしてしまうと共通コードに各プラットフォームごとの分岐処理を記述するのと同様なので、後で特定のプラットフォームだけの修正をしようとすると共通コードに影響したり、修正箇所が広範囲にわたってしまい修正もれを起こしやすくなったりするおそれがあります
上記を防ぐために Unity のプロパティ注入を利用してみます
<!-- 秒針描画 --> <BoxView BindingContext="{Binding SecondHand}" Color="{Binding Color, Converter={StaticResource ColorConverter}}" Opacity="0.5" AbsoluteLayout.LayoutBounds="{Binding LayoutBounds, Converter={StaticResource RectangleConverter}}" AbsoluteLayout.LayoutFlags="None" AnchorX="0.5" AnchorY="0.5" Rotation="{Binding Rotation}"/>
XAML 側はこんな感じに Color プロパティに ViewModel のプロパティをバインドするようにします
ViewModel のプロパティは文字列にしたいので、ColorConverter を作成して string から Color に変換するようにします
<?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="{Binding Color, Converter={StaticResource ColorConverter}}" 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="{Binding Color, Converter={StaticResource ColorConverter}}" 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="{Binding Color, Converter={StaticResource ColorConverter}}" 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="{Binding Color, Converter={StaticResource ColorConverter}}" 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="{Binding Color, Converter={StaticResource ColorConverter}}" 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 全体だとこんな感じ
/// <summary> /// string を Color に変換する Converter /// </summary> public class ColorConverter : IValueConverter { /// <summary> /// string を Color に変換する Converter /// </summary> private static readonly ColorTypeConverter Converter = new ColorTypeConverter(); #region IValueConverter /// <summary> /// 文字列を Color に変換します /// </summary> /// <param name="value">文字列</param> /// <param name="targetType">変換後の型</param> /// <param name="parameter">パラメータ</param> /// <param name="culture">対象カルチャ</param> /// <returns>Color</returns> public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (!(value is string)) { return default(Color); } return Converter.ConvertFrom(CultureInfo.CurrentCulture, value); } /// <summary> /// Color を文字列に変換します /// </summary> /// <param name="value">Color</param> /// <param name="targetType">変換後の型</param> /// <param name="parameter">パラメータ</param> /// <param name="culture">対象カルチャ</param> /// <returns>文字列</returns> public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (!(value is Color)) { return default(string); } var color = ((Color)value); return color.ToString(); } #endregion //IValueConverter }
ちなみに ColorConverter はこんな感じ
あとは iOS の Main.cs、Windows Phone の App.xaml.cs にそれぞれ下記の記述を追加します
public class Application { // This is the main entry point of the application. static void Main(string[] args) { // プラットフォーム依存のプロパティを注入 App.Container .RegisterInstance<string>("HourHandColor", "#FF8469") // 時針の色 .RegisterInstance<string>("MinuteHandColor", "#FF461C") // 分針の色 .RegisterInstance<string>("SecondHandColor", "#CC3816") // 秒針の色 .RegisterType<HandItemViewModel>(new InjectionProperty("Color", "#7F4234")) .RegisterType<TickItemViewModel>(new InjectionProperty("Color", "#7F4234")) // 目盛りの色 .RegisterType<NumberItemViewModel>(new InjectionProperty("Color", "#7F230E"), new InjectionProperty("Number", 0)); // 数字の色 // アプリケーション起動処理 App.Current.OnLaunchApplication(); // if you want to use a different Application Delegate class from "AppDelegate" // you can specify it here. UIApplication.Main(args, null, "AppDelegate"); } }
これは iOS
/// <summary> /// Constructor for the Application object. /// </summary> public App() { // Global handler for uncaught exceptions. UnhandledException += Application_UnhandledException; // プラットフォーム依存のプロパティを注入 XamarinUnityInjection.App.Container .RegisterInstance<string>("HourHandColor", "#08007F") // 時針の色 .RegisterInstance<string>("MinuteHandColor", "#0D00CC") // 分針の色 .RegisterInstance<string>("SecondHandColor", "#1000FF") // 秒針の色 .RegisterType<HandItemViewModel>(new InjectionProperty("Color", "#2C267F")) .RegisterType<TickItemViewModel>(new InjectionProperty("Color", "#2C267F")) // 目盛りの色 .RegisterType<NumberItemViewModel>(new InjectionProperty("Color", "#584CFF"), new InjectionProperty("Number", 0)); // 数字の色 // アプリケーション起動処理 XamarinUnityInjection.App.Current.OnLaunchApplication(); DisplayProperties.AutoRotationPreferences = DisplayOrientations.Landscape | DisplayOrientations.LandscapeFlipped | DisplayOrientations.Portrait | DisplayOrientations.PortraitFlipped; ~ 中略 ~ }
こちらは Windows Phone
Unity のプロパティ注入のカラーコードだけを変えています
/// <summary> /// 最初の画面の ViewModel /// </summary> public class TopPageViewModel : BindableBase { ~ 中略 ~ /// <summary> /// コンストラクタ /// </summary> /// <param name="repository">リポジトリ(DI コンテナにより自動注入される)</param> public TopPageViewModel(IPageStateDetectService pageStateDetectService) { this.pageStateDetectService = pageStateDetectService; // Unity を利用して ViewModel のインスタンスを生成 this.hourHand = App.Container.Resolve<HandItemViewModel>( new PropertyOverride("Color", new ResolvedParameter<string>("HourHandColor"))); this.minuteHand = App.Container.Resolve<HandItemViewModel>( new PropertyOverride("Color", new ResolvedParameter<string>("MinuteHandColor"))); this.secondHand = App.Container.Resolve<HandItemViewModel>( new PropertyOverride("Color", new ResolvedParameter<string>("SecondHandColor"))); this.tickItems.Clear(); for (var i = 0; i < 60; i++) { this.tickItems.Add(App.Container.Resolve<TickItemViewModel>()); } this.numberItems.Clear(); for (var i = 1; i < 13; i++) { this.numberItems.Add(App.Container.Resolve<NumberItemViewModel>(new PropertyOverride("Number", i))); } this.pageStateDetectService.PageAppearing += this.OnPageAppearing; } }
最後に PageViewModel の ViewModel をインスタンス化している箇所
ここで Unity を利用してプロパティ注入しています
さてアプリを実行すると・・・
プラットフォームごとに配色が変わりました!