maui-theming
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese.NET MAUI Theming
.NET MAUI 主题设置
Apply light/dark mode support, custom branded themes, and runtime theme switching in .NET MAUI apps using AppThemeBinding, ResourceDictionary swapping, and system theme detection APIs.
借助AppThemeBinding、ResourceDictionary切换和系统主题检测API,在.NET MAUI应用中实现明暗模式支持、自定义品牌主题以及运行时主题切换。
When to Use
适用场景
- Adding light and dark mode support to a .NET MAUI app
- Creating custom branded themes with ResourceDictionary
- Detecting and responding to system theme changes at runtime
- Letting users choose a preferred theme (light, dark, or system default)
- Combining OS-driven theme response with custom color palettes
- 为.NET MAUI应用添加明暗模式支持
- 使用ResourceDictionary创建自定义品牌主题
- 在运行时检测并响应系统主题变化
- 允许用户选择偏好主题(浅色、深色或系统默认)
- 将系统驱动的主题响应与自定义调色板相结合
When Not to Use
不适用场景
- Localization or language switching — see .NET MAUI localization docs
- Accessibility-specific visual adjustments — see .NET MAUI accessibility docs
- App icon or splash screen configuration — see .NET MAUI app icon docs
- Bootstrap-style class theming — see the NuGet package
Plugin.Maui.BootstrapTheme
- 本地化或语言切换——请参阅.NET MAUI本地化文档
- 无障碍专属视觉调整——请参阅.NET MAUI无障碍文档
- 应用图标或启动屏配置——请参阅.NET MAUI应用图标文档
- Bootstrap风格类主题设置——请查看NuGet包
Plugin.Maui.BootstrapTheme
Inputs
前置条件
- A .NET MAUI project targeting .NET 8 or later
- XAML pages or C# UI code that need theme-aware styling
- 面向.NET 8或更高版本的.NET MAUI项目
- 需要主题感知样式的XAML页面或C# UI代码
Workflow
操作流程
- Detect the current theme approach in the project (AppThemeBinding, ResourceDictionary, or none).
- Choose the appropriate strategy: AppThemeBinding for simple light/dark, ResourceDictionary swap for custom/multiple themes, or both combined.
- Define theme resources — inline values or separate
AppThemeBindingfiles with matching keys.ResourceDictionary - Replace hardcoded colors with bindings (or
DynamicResourcemarkup) throughout XAML pages.AppThemeBinding - Add system theme detection via and the
Application.Current.RequestedThemeevent.RequestedThemeChanged - Implement user preference persistence with /
Preferences.Setand apply on startup.Preferences.Get - Verify Android is set on
ConfigChanges.UiModeto avoid activity restarts on theme change.MainActivity - Test both light and dark themes on at least one target platform, confirming all UI elements respond correctly.
- 检测项目中当前的主题实现方式(AppThemeBinding、ResourceDictionary或无)。
- 选择合适的策略:简单明暗模式用AppThemeBinding,自定义/多主题用ResourceDictionary切换,或两者结合。
- 定义主题资源——内联值或带有匹配键的独立
AppThemeBinding文件。ResourceDictionary - 在所有XAML页面中用绑定(或
DynamicResource标记)替换硬编码颜色。AppThemeBinding - 通过和
Application.Current.RequestedTheme事件添加系统主题检测。RequestedThemeChanged - 使用/
Preferences.Set实现用户偏好持久化,并在启动时应用。Preferences.Get - 验证Android的中是否设置了
MainActivity,避免主题变化时活动重启。ConfigChanges.UiMode - 至少在一个目标平台上测试明暗两种主题,确认所有UI元素都能正确响应。
Choosing an Approach
方案选择
| Approach | Best for | Limitation |
|---|---|---|
| AppThemeBinding | Automatic light/dark with OS — minimal code | Only two themes (light + dark) |
| ResourceDictionary swap | Custom branded themes, more than two themes, user preference | More setup; must use |
| Both combined | OS-driven response plus custom theme colors | Most flexible but most complex |
| 方案 | 最佳适用场景 | 局限性 |
|---|---|---|
| AppThemeBinding | 随系统自动切换明暗模式——代码量极少 | 仅支持两种主题(浅色+深色) |
| ResourceDictionary切换 | 自定义品牌主题、多主题支持、用户偏好设置 | 设置工作量更大;必须全程使用 |
| 两者结合 | 系统驱动响应+自定义主题颜色 | 灵活性最高但复杂度也最高 |
AppThemeBinding (OS Light/Dark)
AppThemeBinding(系统明暗模式)
AppThemeBindingLightDarkDefaultAppThemeBindingLightDarkDefaultXAML
XAML示例
xml
<Label Text="Themed text"
TextColor="{AppThemeBinding Light=Green, Dark=Red}"
BackgroundColor="{AppThemeBinding Light=White, Dark=Black}" />
<!-- With resource references -->
<Label TextColor="{AppThemeBinding Light={StaticResource LightPrimary},
Dark={StaticResource DarkPrimary}}" />xml
<Label Text="主题化文本"
TextColor="{AppThemeBinding Light=Green, Dark=Red}"
BackgroundColor="{AppThemeBinding Light=White, Dark=Black}" />
<!-- 结合资源引用 -->
<Label TextColor="{AppThemeBinding Light={StaticResource LightPrimary},
Dark={StaticResource DarkPrimary}}" />C# Extension Methods
C#扩展方法
csharp
var label = new Label();
// Color-specific helper
label.SetAppThemeColor(Label.TextColorProperty, Colors.Green, Colors.Red);
// Generic helper for any bindable property type
label.SetAppTheme<Color>(Label.TextColorProperty, Colors.Green, Colors.Red);csharp
var label = new Label();
// 颜色专用辅助方法
label.SetAppThemeColor(Label.TextColorProperty, Colors.Green, Colors.Red);
// 适用于任何可绑定属性类型的通用辅助方法
label.SetAppTheme<Color>(Label.TextColorProperty, Colors.Green, Colors.Red);ResourceDictionary Theming (Custom Themes)
ResourceDictionary主题设置(自定义主题)
Use separate files with matching keys to define themes, then swap them at runtime.
ResourceDictionary使用带有匹配键的独立文件定义主题,然后在运行时切换它们。
ResourceDictionaryStep 1 — Define Theme Dictionaries
步骤1——定义主题字典
When using compiled XAML with (as shown below), each dictionary needs a code-behind that calls . Dictionaries loaded via without do not need code-behind.
x:ClassInitializeComponent()Sourcex:ClassLightTheme.xaml
xml
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Themes.LightTheme">
<Color x:Key="PageBackgroundColor">White</Color>
<Color x:Key="PrimaryTextColor">#333333</Color>
<Color x:Key="AccentColor">#2196F3</Color>
</ResourceDictionary>LightTheme.xaml.cs
csharp
namespace MyApp.Themes;
public partial class LightTheme : ResourceDictionary
{
public LightTheme() => InitializeComponent();
}Create a matching DarkTheme.xaml / DarkTheme.xaml.cs with the same keys and different values.
当使用带有的编译XAML(如下所示)时,每个字典都需要一个调用的代码后置文件。通过加载且不带的字典无需代码后置文件。
x:ClassInitializeComponent()Sourcex:ClassLightTheme.xaml
xml
<ResourceDictionary xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Themes.LightTheme">
<Color x:Key="PageBackgroundColor">White</Color>
<Color x:Key="PrimaryTextColor">#333333</Color>
<Color x:Key="AccentColor">#2196F3</Color>
</ResourceDictionary>LightTheme.xaml.cs
csharp
namespace MyApp.Themes;
public partial class LightTheme : ResourceDictionary
{
public LightTheme() => InitializeComponent();
}创建对应的DarkTheme.xaml / DarkTheme.xaml.cs,使用相同的键但不同的值。
Step 2 — Consume with DynamicResource
步骤2——通过DynamicResource使用主题
Use so values update when the dictionary is swapped at runtime:
DynamicResourcexml
<ContentPage BackgroundColor="{DynamicResource PageBackgroundColor}">
<Label Text="Hello"
TextColor="{DynamicResource PrimaryTextColor}" />
<Button Text="Action"
BackgroundColor="{DynamicResource AccentColor}" />
</ContentPage>使用,这样当字典在运行时切换时值会自动更新:
DynamicResourcexml
<ContentPage BackgroundColor="{DynamicResource PageBackgroundColor}">
<Label Text="Hello"
TextColor="{DynamicResource PrimaryTextColor}" />
<Button Text="操作"
BackgroundColor="{DynamicResource AccentColor}" />
</ContentPage>Step 3 — Switch Themes at Runtime
步骤3——运行时切换主题
csharp
void ApplyTheme(ResourceDictionary theme)
{
// Assumes theme dictionaries are the only merged dictionaries.
// If your App.xaml merges non-theme dictionaries (e.g., converters),
// move them to Application.Resources directly instead.
var mergedDictionaries = Application.Current!.Resources.MergedDictionaries;
mergedDictionaries.Clear();
mergedDictionaries.Add(theme);
}
// Usage
ApplyTheme(new DarkTheme());csharp
void ApplyTheme(ResourceDictionary theme)
{
// 假设主题字典是唯一合并的字典。
// 如果你的App.xaml合并了非主题字典(例如转换器),请将它们直接移至Application.Resources中。
var mergedDictionaries = Application.Current!.Resources.MergedDictionaries;
mergedDictionaries.Clear();
mergedDictionaries.Add(theme);
}
// 使用示例
ApplyTheme(new DarkTheme());System Theme Detection
系统主题检测
Read the Current Theme
读取当前主题
csharp
AppTheme currentTheme = Application.Current!.RequestedTheme;
// Returns AppTheme.Light, AppTheme.Dark, or AppTheme.Unspecifiedcsharp
AppTheme currentTheme = Application.Current!.RequestedTheme;
// 返回AppTheme.Light、AppTheme.Dark或AppTheme.UnspecifiedOverride the System Theme
覆盖系统主题
csharp
// Force dark mode regardless of OS setting
Application.Current!.UserAppTheme = AppTheme.Dark;
// Reset to follow system theme
Application.Current!.UserAppTheme = AppTheme.Unspecified;csharp
// 强制深色模式,忽略系统设置
Application.Current!.UserAppTheme = AppTheme.Dark;
// 重置为跟随系统主题
Application.Current!.UserAppTheme = AppTheme.Unspecified;React to Theme Changes
响应主题变化
csharp
Application.Current!.RequestedThemeChanged += (s, e) =>
{
AppTheme newTheme = e.RequestedTheme;
// Update UI or switch ResourceDictionaries
};csharp
Application.Current!.RequestedThemeChanged += (s, e) =>
{
AppTheme newTheme = e.RequestedTheme;
// 更新UI或切换ResourceDictionary
};Combining Both Approaches
两种方案结合使用
Use with values for maximum flexibility:
AppThemeBindingDynamicResourcexml
<Label TextColor="{AppThemeBinding
Light={DynamicResource LightPrimary},
Dark={DynamicResource DarkPrimary}}" />Or react to system changes and swap full dictionaries:
csharp
Application.Current!.RequestedThemeChanged += (s, e) =>
{
ApplyTheme(e.RequestedTheme == AppTheme.Dark
? new DarkTheme()
: new LightTheme());
};将与值结合使用以获得最大灵活性:
AppThemeBindingDynamicResourcexml
<Label TextColor="{AppThemeBinding
Light={DynamicResource LightPrimary},
Dark={DynamicResource DarkPrimary}}" />或者响应系统变化并切换完整字典:
csharp
Application.Current!.RequestedThemeChanged += (s, e) =>
{
ApplyTheme(e.RequestedTheme == AppTheme.Dark
? new DarkTheme()
: new LightTheme());
};Saving and Restoring User Preference
保存与恢复用户偏好
Store the user's choice with and apply it on startup:
Preferencescsharp
// Save choice
Preferences.Set("AppTheme", "Dark");
// Restore on startup (in App constructor or CreateWindow)
var saved = Preferences.Get("AppTheme", "System");
Application.Current!.UserAppTheme = saved switch
{
"Light" => AppTheme.Light,
"Dark" => AppTheme.Dark,
_ => AppTheme.Unspecified
};使用存储用户选择,并在启动时应用:
Preferencescsharp
// 保存选择
Preferences.Set("AppTheme", "Dark");
// 启动时恢复(在App构造函数或CreateWindow中)
var saved = Preferences.Get("AppTheme", "System");
Application.Current!.UserAppTheme = saved switch
{
"Light" => AppTheme.Light,
"Dark" => AppTheme.Dark,
_ => AppTheme.Unspecified
};Common Pitfalls
常见陷阱
Android: ConfigChanges.UiMode is Required
Android:必须设置ConfigChanges.UiMode
MainActivityConfigChanges.UiModecsharp
[Activity(Theme = "@style/Maui.SplashTheme",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize
| ConfigChanges.Orientation
| ConfigChanges.UiMode // ← Required for theme detection
| ConfigChanges.ScreenLayout
| ConfigChanges.SmallestScreenSize
| ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity { }Without , toggling dark mode in Android settings causes a full activity restart — losing navigation state and appearing as a crash.
UiModeMainActivityConfigChanges.UiModecsharp
[Activity(Theme = "@style/Maui.SplashTheme",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize
| ConfigChanges.Orientation
| ConfigChanges.UiMode // ← 主题检测必需
| ConfigChanges.ScreenLayout
| ConfigChanges.SmallestScreenSize
| ConfigChanges.Density)]
public class MainActivity : MauiAppCompatActivity { }如果没有,在Android设置中切换深色模式会导致活动完全重启——丢失导航状态,看起来像崩溃。
UiModeDynamicResource vs StaticResource
DynamicResource vs StaticResource
When using ResourceDictionary theme switching, you must use :
DynamicResourcexml
<!-- ✅ Updates when theme dictionary changes -->
<Label TextColor="{DynamicResource PrimaryTextColor}" />
<!-- ❌ Frozen at first load — won't update on theme switch -->
<Label TextColor="{StaticResource PrimaryTextColor}" />使用ResourceDictionary主题切换时,必须使用:
DynamicResourcexml
<!-- ✅ 主题字典变化时会更新 -->
<Label TextColor="{DynamicResource PrimaryTextColor}" />
<!-- ❌ 首次加载后固定不变——主题切换时不会更新 -->
<Label TextColor="{StaticResource PrimaryTextColor}" />Hardcoded Colors Break Theming
硬编码颜色会破坏主题设置
Avoid inline color values on elements that should respect the theme:
xml
<!-- ❌ Will not change with theme -->
<Label TextColor="#333333" />
<!-- ✅ Theme-aware -->
<Label TextColor="{DynamicResource PrimaryTextColor}" />避免在应遵循主题的元素上使用内联颜色值:
xml
<!-- ❌ 不会随主题变化 -->
<Label TextColor="#333333" />
<!-- ✅ 支持主题感知 -->
<Label TextColor="{DynamicResource PrimaryTextColor}" />CSS Themes Cannot Be Swapped at Runtime
CSS主题无法在运行时切换
.NET MAUI supports CSS styling, but CSS-based themes cannot be swapped dynamically. Use ResourceDictionary theming for runtime switching.
.NET MAUI支持CSS样式,但基于CSS的主题无法动态切换。如需运行时切换,请使用ResourceDictionary主题设置。
Theme Keys Must Match Across Dictionaries
主题键必须在所有字典中匹配
Every used in one theme dictionary must exist in all other theme dictionaries. A missing key causes a silent fallback to the default value, leading to inconsistent appearance.
x:Key一个主题字典中使用的每个必须存在于所有其他主题字典中。缺失的键会导致静默回退到默认值,导致外观不一致。
x:KeyPlatform Support
平台支持
| Platform | Minimum Version |
|---|---|
| iOS | 13+ |
| Android | 10+ (API 29) |
| macOS Catalyst | 10.15+ |
| Windows | 10+ |
| 平台 | 最低版本 |
|---|---|
| iOS | 13+ |
| Android | 10+(API 29) |
| macOS Catalyst | 10.15+ |
| Windows | 10+ |
Quick Reference
快速参考
- OS light/dark → markup extension
AppThemeBinding - Theme colors in C# → ,
SetAppThemeColor()SetAppTheme<T>() - Read OS theme →
Application.Current.RequestedTheme - Force theme →
Application.Current.UserAppTheme = AppTheme.Dark - Theme changes → event
RequestedThemeChanged - Custom switching → Swap in
ResourceDictionaryMergedDictionaries - Runtime bindings → (not
DynamicResource)StaticResource - Persist choice → /
Preferences.SetPreferences.Get
- 系统明暗模式 → 标记扩展
AppThemeBinding - C#中的主题颜色 → 、
SetAppThemeColor()SetAppTheme<T>() - 读取系统主题 →
Application.Current.RequestedTheme - 强制主题 →
Application.Current.UserAppTheme = AppTheme.Dark - 主题变化 → 事件
RequestedThemeChanged - 自定义切换 → 在中替换
MergedDictionariesResourceDictionary - 运行时绑定 → (而非
DynamicResource)StaticResource - 持久化选择 → /
Preferences.SetPreferences.Get