Xamarin.Formsのダークテーマ対応でAppThemeBindingを使うようにした
2021年10月14日
2021年10月17日
以前のXamarin.Formsのダークモード検出にXamarin.EssentialsのAppInfo.RequestedThemeを使うようにしたとXamarin.Formsでダークモード対応したをさらに改良。
端末のダークモード(ダークテーマ)に関わらず、モードを固定したいという要望があり。。
【やったこと】
1.イメージは、XAMLでAppThemeBindingで指定する。
というのも、iOSのアセットカタログ、Androidのdrawable-nightにリソースを作ると、アプリと端末のダークモードが違う場合にうまく切り替わってくれないので、ベタにResoucesに入れる。
1 |
<Image Source = "{AppThemeBinding Light=printer.png, Dark=printer_dark.png}" Margin="5,0,0,0"/> |
これXamarin5.0からだっけ?
※Androidは、Activity#recreate()することで切り替えられるが、結局アプリ再起動?なので初期化処理に時間がかかる問題がある。
2.各プラットフォームにモードを設定するDependencyServiceを作る。
これしないとキーボードが端末側のモードで出ちゃうので。
1 2 3 4 5 6 7 8 9 10 11 12 |
public enum Theme { Auto, Light, Dark, } public interface ISystemThemeService { void SetTheme(Theme theme, bool restart=false); Theme GetTheme(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class SystemThemeService : ISystemThemeService { public Theme GetTheme() { return UITraitCollection.CurrentTraitCollection.UserInterfaceStyle switch { UIUserInterfaceStyle.Dark => Theme.Dark, _ => Theme.Light }; } public void SetTheme(Theme theme, bool restart = false) { UIApplication.SharedApplication.KeyWindow.OverrideUserInterfaceStyle = theme switch { Theme.Dark => UIUserInterfaceStyle.Dark, Theme.Light => UIUserInterfaceStyle.Light, _ => UITraitCollection.CurrentTraitCollection.UserInterfaceStyle }; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class SystemThemeService : ISystemThemeService { public static Context Context = Platform.AppContext; public Theme GetTheme() { var currentMode = Context.Resources.Configuration.UiMode & UiMode.NightMask; return currentMode switch { UiMode.NightYes => Theme.Dark, _ => Theme.Light }; } public void SetTheme(Theme theme, bool restart = false) { AppCompatDelegate.DefaultNightMode = theme switch { Theme.Dark => AppCompatDelegate.ModeNightYes, Theme.Light => AppCompatDelegate.ModeNightNo, _ => AppCompatDelegate.ModeNightFollowSystem, }; } } |
(参考サイトを丸パクリ)
3.App.xaml.csで切り替え制御する。
テーマ制御は以前作ったのと同様。
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 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
public partial class App : PrismApplication { private static Theme _Theme; public static void ApplyTheme(bool isRedraw = false) { var systemThemeService = App.Current?.Container?.Resolve<ISystemThemeService>(); _Theme = systemThemeService?.GetTheme() ?? Theme.Auto; ChangeTheme(isRedraw); } public static void ChangeTheme(bool isRedraw = false) { var statusService = App.Current?.Container?.Resolve<IStatusService>(); var systemThemeService = App.Current?.Container?.Resolve<ISystemThemeService>(); bool isThemeChamged; if (statusService?.LocalSetting.Value.AppTheme == Theme.Light) { Application.Current.UserAppTheme = OSAppTheme.Light; isThemeChamged = SetDictionary(Theme.Light); systemThemeService?.SetTheme(Theme.Light, isRedraw); } else if (statusService?.LocalSetting.Value.AppTheme == Theme.Dark) { Application.Current.UserAppTheme = OSAppTheme.Dark; isThemeChamged = SetDictionary(Theme.Dark); systemThemeService?.SetTheme(Theme.Dark, isRedraw); } else { if (_Theme != Theme.Dark) { isThemeChamged = SetDictionary(Theme.Light); Application.Current.UserAppTheme = OSAppTheme.Light; systemThemeService?.SetTheme(Theme.Light); } else { isThemeChamged = SetDictionary(Theme.Dark); Application.Current.UserAppTheme = OSAppTheme.Dark; systemThemeService?.SetTheme(Theme.Dark); } } //Resourceを変更したら、再描画する。 if (isRedraw && isThemeChamged) { var ea = App.Current?.Container?.Resolve<IEventAggregator>(); ea?.GetEvent<ConfigurationChangedEvent>().Publish(); } } static bool SetDictionary(Theme theme) { switch (theme) { case Theme.Light: if (App.Current.Resources.GetType() != typeof(LightTheme)) { App.Current.Resources = new LightTheme(); isChanged = true; } break; case Theme.Dark: if (App.Current.Resources.GetType() != typeof(DarkTheme)) { App.Current.Resources = new DarkTheme(); isChanged = true; } break; } return isChanged; } protected override async void OnStart() { ApplyTheme(); } protected override void OnResume() { base.OnResume(); ApplyTheme(true); } |
4.設定画面で変更時に処理を呼び出す。
こんな感じ。設定画面のVMからAppのstaticメソッド呼び出しにした。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
DisappearingCommand.Subscribe(() => { if (LocalSetting.Value.IsDirty(_IStatusService.LocalSetting.Value)) { var isTemeChanged = LocalSetting.Value.AppTheme != _IStatusService.LocalSetting.Value.AppTheme; _IStatusService.LocalSetting.Value = LocalSetting.Value.Clone(); _IStatusService.SaveSetting(); if (isTemeChanged) App.ChangeTheme(true); } }); |
できた(∩´∀`)∩
【参考サイト】
ライブラリで勝手にお世話になってるkamu様→Xamarin.Forms で ダークモードにほぼ完全に対応し、「端末の設定を使う」にも対応する
MS公式→アプリケーションのシステム テーマの変更に Xamarin.Forms 対応する
2件のピンバック
Xamarin.Formsのダークモード検出にXamarin.EssentialsのAppInfo.RequestedThemeを使うようにした | blog
iOS13より前の端末でUITraitCollectionを使うと | blog