Xamarin.Formsでダークモード対応した
更新記事があります Xamarin.Formsのダークモード検出にXamarin.EssentialsのAppInfo.RequestedThemeを使うようにした |
iOSはダークモード、Androidはナイトモード?
iOS13の新機能で気になっていたけど、Androidは前からあるんだな。
Xamarin.Formsは自動でダークモードにならないので、XAMLの色指定をDynamicResourceにして切り替えるとのこと。
【参考サイト】Check for Dark Mode in Xamarin.Forms
を見て対応した。
Xamarin.Forms
モードを検出してResourceを差し替えるコード
App.xaml.csに↑のDependencyServiceから取得したモードによってリソースを差し替えるコードを追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
//↓Initialize()でこれだと起動時に落ちるっぽい //public override async void Initialize() //{ // base.Initialize(); // // // ダークモード対応 // var theme = await Container.Resolve<IEnvironment>().GetOperatingSystemTheme(); // await SetTheme(theme); //} //↓なのでOnStart()でやった。参考サイトもそうしてるな。。 protected override async void OnStart() { await Task.Run(async () => { await SetTheme(); }); base.OnStart(); } protected override async void OnResume() { base.OnResume(); await SetTheme(true); } //↓Androidのバックグラウンド時にthemeが戻されちゃうケースがあるので使うのやめた。 //public static ReactiveProperty<Theme> AppTheme { get; } = new ReactiveProperty<Theme>(); async Task SetTheme(bool isRedraw = false) { await Task.Run(() => { Device.BeginInvokeOnMainThread(async () => { var theme = await Container.Resolve<IEnvironment>().GetOperatingSystemTheme(); if (theme == Theme.Dark) { if (App.Current.Resources is DarkTheme) return; App.Current.Resources = new DarkTheme(); //App.AppTheme.Value = Theme.Dark; if (isRedraw) { var ea = Container.Resolve<IEventAggregator>(); ea.GetEvent<BloodListRefreashEvent>().Publish(); } } else { if (!(App.Current.Resources is DarkTheme)) return; App.Current.Resources = new LightTheme(); //App.AppTheme.Value = Theme.Light; if (isRedraw) { var ea = Container.Resolve<IEventAggregator>(); ea.GetEvent<BloodListRefreashEvent>().Publish(); } } }); }); } |
※(2019/12/23追記)Initialize()でやると起動時に落ちる現象があるっぽいのでOnStart()で。チェックもBeginInvokeOnMainThreadでやることにした。Taskでasync voidをawaitしてるのがいかんのかなぁ。いつか頑張る。
※(2019/12/30追記)Androidでバックボタンで閉じたり、ホームボタンで閉じて設定いじるとライトモードのリソースがセットされてしまうので、App.AppTheme.Valueで判定するのをやめた。<( ̄- ̄)>
OnResume()では、アプリ起動中にモード変更したときのために、各画面に再描画イベントを投げるようにした。
AppThemeが ReactivePropertyなので、各画面でSubscribeしてやるって手もあるが。(-_-;ウーン
BeginInvokeOnMainThreadしてるのは、Androidが変わってくれなかったため。。。
Resource
もともとのResourceは1ファイルにしてたのをDefaultThemeの中のMergedDictionariesに入れて。
1 2 3 4 5 6 7 8 |
<?xml version="1.0" encoding="UTF-8"?> <ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyApp.Views.Themes.DefaultTheme"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="/Views/Themes/AppStyles.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> |
それをSourceにしたDarkThemeとLightThemeを作り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?xml version="1.0" encoding="UTF-8"?> <ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyApp.Views.Themes.DarkTheme" Source="DefaultTheme.xaml"> <Color x:Key="backgroundColor">#FF000000</Color> <Color x:Key="AppViewBackgroundColor">#183358</Color> <Color x:Key="ButtonBackgroundColor">#112666</Color> <Color x:Key="ButtonBorderColor">#166282</Color> <Color x:Key="ButtonTextColor">#C4C4C5</Color> <Color x:Key="GroupHeaderBackgroundColor">#232326</Color> <Color x:Key="GroupHeaderBorderColor">#3C3C3F</Color> <Color x:Key="HeaderBackgroundColor">#1e2e55</Color> <Color x:Key="HeaderBorderColor">#3C3C3F</Color> <Color x:Key="HeaderTextColor">#C4C4C5</Color> <Color x:Key="ListSelectedColor">#232326</Color> <Color x:Key="ListBorderColor">#3C3C3F</Color> <Color x:Key="CalenderPopupBackgroundColor">#C0255089</Color> <Color x:Key="CalenderHeaderBackgroundColor">#37373b</Color> <Color x:Key="CalenderCellOtherMonthBackgroundColor">#232326</Color> <Color x:Key="CalenderCellSelectedTextColor">Black</Color> <Color x:Key="GraphGridColor">#4f4f52</Color> <Color x:Key="SettingBacgroundColor">#000000</Color> <Color x:Key="SettingListBacgroundColor">#1C1C1E</Color> <Color x:Key="ActivityIndicatorColor">White</Color> <Color x:Key="TextBlueColor">#0A84FF</Color> <OnPlatform x:Key="TabbarBackground" x:TypeArguments="Color" iOS="Default" Android="#575757"/> <Color x:Key="TabbarTextColor">#8A8A8A</Color> <Color x:Key="TabbarSelectedColor">#1C81F3</Color> <Color x:Key="TextPrimaryColor">#E6E6E6</Color> <Color x:Key="TextSecondaryColor">#C4C4C5</Color> <Color x:Key="TextTernaryColor">#C8C8C8</Color> </ResourceDictionary> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
<?xml version="1.0" encoding="UTF-8"?> <ResourceDictionary xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MyApp.Views.Themes.LightTheme" Source="DefaultTheme.xaml"> <Color x:Key="backgroundColor">#FFFFFFFF</Color> <Color x:Key="AppViewBackgroundColor">#437ECC</Color> <Color x:Key="ButtonBackgroundColor">RoyalBlue</Color> <Color x:Key="ButtonBorderColor">SkyBlue</Color> <Color x:Key="ButtonTextColor">White</Color> <Color x:Key="GroupHeaderBackgroundColor">#F0EFF5</Color> <Color x:Key="GroupHeaderBorderColor">Black</Color> <Color x:Key="HeaderBackgroundColor">Navy</Color> <Color x:Key="HeaderBorderColor">White</Color> <Color x:Key="HeaderTextColor">White</Color> <Color x:Key="ListSelectedColor">#D9D9D9</Color> <Color x:Key="ListBorderColor">Black</Color> <Color x:Key="CalenderPopupBackgroundColor">#C0437ECC</Color> <Color x:Key="CalenderHeaderBackgroundColor">LightGray</Color> <Color x:Key="CalenderCellOtherMonthBackgroundColor">#EFEFF2</Color> <Color x:Key="CalenderCellSelectedTextColor">White</Color> <Color x:Key="GraphGridColor">#a9a9a9</Color> <Color x:Key="SettingBacgroundColor">#F0EFF5</Color> <Color x:Key="SettingListBacgroundColor">White</Color> <Color x:Key="ActivityIndicatorColor">Default</Color> <Color x:Key="TextBlueColor">Blue</Color> <OnPlatform x:Key="TabbarBackground" x:TypeArguments="Color" iOS="Default" Android="#F7F7F7"/> <Color x:Key="TabbarTextColor">#959595</Color> <Color x:Key="TabbarSelectedColor">#1C81F3</Color> <Color x:Key="TextPrimaryColor">#000000</Color> <Color x:Key="TextSecondaryColor">#000000</Color> <Color x:Key="TextTernaryColor">#C8C8C8</Color> </ResourceDictionary> |
App.xamlではLightThemeを指定しとく。
1 2 3 4 5 6 7 8 9 |
<?xml version="1.0" encoding="utf-8" ?> <prism:PrismApplication xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:themes="clr-namespace:MyApp.Views.Themes" x:Class="health.App"> <Application.Resources> <ResourceDictionary Source="/Views/Themes/LightTheme.xaml" /> </Application.Resources> </prism:PrismApplication> |
これがSetTheme()で切り替わるって作り。(o^-‘)b グッ!
iOS
各モード用のアイコンを用意する
アイコンを一個一個アセットカタログにして。。。アセットカタログ変わってる。!∑(゜∀゜)
ダークテーマとか標準とか入れられるようになってる。
独自実装した画面とか(SnackBarとか)は、中でApp.Theme見て色変えるとかしないといけないけど、ダイアログとか勝手に変わってくれる。
Android
各モード用のアイコンを用意する
アイコンは、フォルダ名に-nightってつけたものを作って置いとけば自動で使ってくれる。
Style
各モード用のStyleを-nightつけたフォルダに入れる。
styles.xmlは、parent=”Theme.AppCompat.DayNight”にすると、DatePickerとかAlertとか自動でダークモードになってくれる。
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="utf-8" ?> <resources> <style name="MainTheme" parent="MainTheme.Base"> </style> <!-- Base theme applied no matter what API --> <style name="MainTheme.Base" parent="Theme.AppCompat.DayNight"> <!--If you are using revision 22.1 please use just windowNoTitle. Without android:--> <item name="windowNoTitle">true</item> <!--We will be using the toolbar so no need to show ActionBar--> <item name="windowActionBar">false</item> |
1 2 3 4 5 6 7 8 9 10 |
<?xml version="1.0" encoding="utf-8" ?> <resources> <style name="MainTheme" parent="MainTheme.Base"> </style> <!-- Base theme applied no matter what API --> <style name="MainTheme.Base" parent="Theme.AppCompat.Light.DarkActionBar"> <!--If you are using revision 22.1 please use just windowNoTitle. Without android:--> <item name="windowNoTitle">true</item> <!--We will be using the toolbar so no need to show ActionBar--> <item name="windowActionBar">false</item> |
カスタムPickerとか
コードで指定するstyleを各フォルダ(-nightフォルダ)に置いて指定する。
1 2 3 4 |
var builder = new Android.Support.V7.App.AlertDialog.Builder(Context, Resource.Style.YearMonthPickerTheme); builder.SetView(layout); builder.SetTitle($"{year}年{month}月"); builder.SetNegativeButton("キャンセル", (s, a) => |
Style
1 2 3 4 5 6 7 8 9 10 11 |
<style name="MainTheme.Base" parent="Theme.AppCompat.DayNight"> : <style name="YearMonthPickerTheme" parent="Theme.AppCompat.Light.Dialog"> <item name="android:windowMinWidthMajor">0%</item> <item name="android:windowMinWidthMinor">0%</item> <item name="android:background">#4C4C4C</item> <item name="android:textColorPrimary">#E6E6E6</item> <item name="android:headerBackground">#4C4C4C</item> </style> |
うん、良い感じだな。(o^-‘)b グッ!