mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-20 23:54:26 +08:00
609 lines
18 KiB
Markdown
609 lines
18 KiB
Markdown
|
|
# 主题系统
|
|||
|
|
|
|||
|
|
本文档详细说明如何在组件中实现主题切换,确保组件完美适配亮色和暗色主题。
|
|||
|
|
|
|||
|
|
## 🎨 主题系统概述
|
|||
|
|
|
|||
|
|
阑山桌面支持以下主题:
|
|||
|
|
- **亮色主题(Light Theme)** - 默认主题,适合白天使用
|
|||
|
|
- **暗色主题(Dark Theme)** - 保护眼睛,适合夜间使用
|
|||
|
|
- **跟随系统** - 自动跟随 Windows 系统主题
|
|||
|
|
|
|||
|
|
## 🏗️ 主题架构
|
|||
|
|
|
|||
|
|
### 主题资源结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Themes/
|
|||
|
|
├── LightTheme.axaml # 亮色主题资源
|
|||
|
|
├── DarkTheme.axaml # 暗色主题资源
|
|||
|
|
└── Common.axaml # 通用资源(尺寸、字体等)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 资源字典加载
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<Application.Styles>
|
|||
|
|
<!-- 通用资源 -->
|
|||
|
|
<StyleInclude Source="avares://LanMountainDesktop/Themes/Common.axaml"/>
|
|||
|
|
|
|||
|
|
<!-- 主题资源(动态加载) -->
|
|||
|
|
<StyleInclude Source="{DynamicResource CurrentTheme}"/>
|
|||
|
|
</Application.Styles>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 💡 亮色主题(Light Theme)
|
|||
|
|
|
|||
|
|
### 完整颜色定义
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
|||
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|||
|
|
|
|||
|
|
<!-- ========== 背景色 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="DesktopBackgroundBrush" Color="#F3F3F3"/>
|
|||
|
|
<SolidColorBrush x:Key="CardBackgroundBrush" Color="#FFFFFF"/>
|
|||
|
|
<SolidColorBrush x:Key="CardBackgroundSecondaryBrush" Color="#F9F9F9"/>
|
|||
|
|
<SolidColorBrush x:Key="CardBackgroundHoverBrush" Color="#F3F3F3"/>
|
|||
|
|
<SolidColorBrush x:Key="CardBackgroundPressedBrush" Color="#E8E8E8"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 文本色 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorPrimaryBrush" Color="#1C1C1C"/>
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorSecondaryBrush" Color="#616161"/>
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorTertiaryBrush" Color="#8E8E8E"/>
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorDisabledBrush" Color="#C7C7C7"/>
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorInverseBrush" Color="#FFFFFF"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 强调色 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="AccentBrush" Color="#0078D4"/>
|
|||
|
|
<SolidColorBrush x:Key="AccentHoverBrush" Color="#106EBE"/>
|
|||
|
|
<SolidColorBrush x:Key="AccentPressedBrush" Color="#005A9E"/>
|
|||
|
|
<SolidColorBrush x:Key="AccentDisabledBrush" Color="#80BCEB"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 语义色 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="SuccessBrush" Color="#107C10"/>
|
|||
|
|
<SolidColorBrush x:Key="WarningBrush" Color="#FF8C00"/>
|
|||
|
|
<SolidColorBrush x:Key="ErrorBrush" Color="#E81123"/>
|
|||
|
|
<SolidColorBrush x:Key="InfoBrush" Color="#0078D4"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 边框与分割线 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="CardBorderBrush" Color="#E0E0E0"/>
|
|||
|
|
<SolidColorBrush x:Key="DividerBrush" Color="#EBEBEB"/>
|
|||
|
|
<SolidColorBrush x:Key="FocusBorderBrush" Color="#0078D4"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 输入框 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="TextBoxBackgroundBrush" Color="#FFFFFF"/>
|
|||
|
|
<SolidColorBrush x:Key="TextBoxBorderBrush" Color="#E0E0E0"/>
|
|||
|
|
<SolidColorBrush x:Key="TextBoxBorderHoverBrush" Color="#C0C0C0"/>
|
|||
|
|
<SolidColorBrush x:Key="TextBoxBorderFocusBrush" Color="#0078D4"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 覆盖层 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="OverlayBrush" Color="#80000000"/>
|
|||
|
|
<SolidColorBrush x:Key="TooltipBackgroundBrush" Color="#F9F9F9"/>
|
|||
|
|
|
|||
|
|
</ResourceDictionary>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 亮色主题示例
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────┐
|
|||
|
|
│ ░░░░░░░░░ #F3F3F3 ░░░░░░░░░ │ 桌面背景
|
|||
|
|
│ ┌────────────────────────────┐ │
|
|||
|
|
│ │ 📍 北京 #1C1C1C │ │ 主要文本
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ ☀️ │ │
|
|||
|
|
│ │ 25°C #1C1C1C │ │
|
|||
|
|
│ │ 晴天 #616161 │ │ 次要文本
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ 今天天气不错 #8E8E8E │ │ 辅助文本
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ [🔄] [⚙️] │ │
|
|||
|
|
│ └────────────────────────────┘ │
|
|||
|
|
│ #FFFFFF 卡片背景 │
|
|||
|
|
└──────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🌙 暗色主题(Dark Theme)
|
|||
|
|
|
|||
|
|
### 完整颜色定义
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
|||
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|||
|
|
|
|||
|
|
<!-- ========== 背景色 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="DesktopBackgroundBrush" Color="#202020"/>
|
|||
|
|
<SolidColorBrush x:Key="CardBackgroundBrush" Color="#2C2C2C"/>
|
|||
|
|
<SolidColorBrush x:Key="CardBackgroundSecondaryBrush" Color="#343434"/>
|
|||
|
|
<SolidColorBrush x:Key="CardBackgroundHoverBrush" Color="#3A3A3A"/>
|
|||
|
|
<SolidColorBrush x:Key="CardBackgroundPressedBrush" Color="#404040"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 文本色 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorPrimaryBrush" Color="#FFFFFF"/>
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorSecondaryBrush" Color="#C8C8C8"/>
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorTertiaryBrush" Color="#8E8E8E"/>
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorDisabledBrush" Color="#5E5E5E"/>
|
|||
|
|
<SolidColorBrush x:Key="TextFillColorInverseBrush" Color="#1C1C1C"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 强调色 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="AccentBrush" Color="#60CDFF"/>
|
|||
|
|
<SolidColorBrush x:Key="AccentHoverBrush" Color="#3DB8FF"/>
|
|||
|
|
<SolidColorBrush x:Key="AccentPressedBrush" Color="#1AA7FF"/>
|
|||
|
|
<SolidColorBrush x:Key="AccentDisabledBrush" Color="#306680"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 语义色 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="SuccessBrush" Color="#6CCB5F"/>
|
|||
|
|
<SolidColorBrush x:Key="WarningBrush" Color="#FCE100"/>
|
|||
|
|
<SolidColorBrush x:Key="ErrorBrush" Color="#FF99A4"/>
|
|||
|
|
<SolidColorBrush x:Key="InfoBrush" Color="#60CDFF"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 边框与分割线 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="CardBorderBrush" Color="#3F3F3F"/>
|
|||
|
|
<SolidColorBrush x:Key="DividerBrush" Color="#3A3A3A"/>
|
|||
|
|
<SolidColorBrush x:Key="FocusBorderBrush" Color="#60CDFF"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 输入框 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="TextBoxBackgroundBrush" Color="#2C2C2C"/>
|
|||
|
|
<SolidColorBrush x:Key="TextBoxBorderBrush" Color="#3F3F3F"/>
|
|||
|
|
<SolidColorBrush x:Key="TextBoxBorderHoverBrush" Color="#505050"/>
|
|||
|
|
<SolidColorBrush x:Key="TextBoxBorderFocusBrush" Color="#60CDFF"/>
|
|||
|
|
|
|||
|
|
<!-- ========== 覆盖层 ========== -->
|
|||
|
|
<SolidColorBrush x:Key="OverlayBrush" Color="#80000000"/>
|
|||
|
|
<SolidColorBrush x:Key="TooltipBackgroundBrush" Color="#343434"/>
|
|||
|
|
|
|||
|
|
</ResourceDictionary>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 暗色主题示例
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌──────────────────────────────────┐
|
|||
|
|
│ ▓▓▓▓▓▓▓▓▓ #202020 ▓▓▓▓▓▓▓▓▓ │ 桌面背景
|
|||
|
|
│ ┌────────────────────────────┐ │
|
|||
|
|
│ │ 📍 北京 #FFFFFF │ │ 主要文本
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ ☀️ │ │
|
|||
|
|
│ │ 25°C #FFFFFF │ │
|
|||
|
|
│ │ 晴天 #C8C8C8 │ │ 次要文本
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ 今天天气不错 #8E8E8E │ │ 辅助文本
|
|||
|
|
│ │ │ │
|
|||
|
|
│ │ [🔄] [⚙️] │ │
|
|||
|
|
│ └────────────────────────────┘ │
|
|||
|
|
│ #2C2C2C 卡片背景 │
|
|||
|
|
└──────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔄 主题切换实现
|
|||
|
|
|
|||
|
|
### 在组件中使用主题资源
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<Border Background="{DynamicResource CardBackgroundBrush}"
|
|||
|
|
BorderBrush="{DynamicResource CardBorderBrush}"
|
|||
|
|
BorderThickness="1"
|
|||
|
|
CornerRadius="8"
|
|||
|
|
Padding="16">
|
|||
|
|
|
|||
|
|
<StackPanel Spacing="8">
|
|||
|
|
<!-- 标题 -->
|
|||
|
|
<TextBlock Text="天气预报"
|
|||
|
|
FontSize="16"
|
|||
|
|
FontWeight="SemiBold"
|
|||
|
|
Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
|
|||
|
|
|
|||
|
|
<!-- 内容 -->
|
|||
|
|
<TextBlock Text="今天天气晴朗"
|
|||
|
|
FontSize="14"
|
|||
|
|
Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
|
|||
|
|
|
|||
|
|
<!-- 按钮 -->
|
|||
|
|
<Button Content="刷新"
|
|||
|
|
Background="{DynamicResource AccentBrush}"
|
|||
|
|
Foreground="White"/>
|
|||
|
|
</StackPanel>
|
|||
|
|
|
|||
|
|
</Border>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 关键点:使用 DynamicResource
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- ✅ 正确:使用 DynamicResource -->
|
|||
|
|
<Border Background="{DynamicResource CardBackgroundBrush}">
|
|||
|
|
<!-- 会响应主题切换 -->
|
|||
|
|
</Border>
|
|||
|
|
|
|||
|
|
<!-- ❌ 错误:使用 StaticResource -->
|
|||
|
|
<Border Background="{StaticResource CardBackgroundBrush}">
|
|||
|
|
<!-- 不会响应主题切换 -->
|
|||
|
|
</Border>
|
|||
|
|
|
|||
|
|
<!-- ❌ 错误:硬编码颜色 -->
|
|||
|
|
<Border Background="#FFFFFF">
|
|||
|
|
<!-- 完全不支持主题 -->
|
|||
|
|
</Border>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 监听主题变更
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class WeatherComponent : ComponentBase
|
|||
|
|
{
|
|||
|
|
public override async Task InitializeAsync()
|
|||
|
|
{
|
|||
|
|
// 订阅主题变更事件
|
|||
|
|
var themeService = Services.GetService<IThemeService>();
|
|||
|
|
if (themeService != null)
|
|||
|
|
{
|
|||
|
|
themeService.ThemeChanged += OnThemeChanged;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnThemeChanged(object? sender, ThemeChangedEventArgs e)
|
|||
|
|
{
|
|||
|
|
Logger.LogInformation($"Theme changed to: {e.NewTheme}");
|
|||
|
|
|
|||
|
|
// 执行主题切换后的逻辑
|
|||
|
|
// 例如:重新加载图片、调整布局等
|
|||
|
|
UpdateForTheme(e.NewTheme);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void UpdateForTheme(Theme theme)
|
|||
|
|
{
|
|||
|
|
if (theme == Theme.Dark)
|
|||
|
|
{
|
|||
|
|
// 暗色主题特殊处理
|
|||
|
|
LoadDarkThemeIcon();
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
// 亮色主题特殊处理
|
|||
|
|
LoadLightThemeIcon();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override void Dispose()
|
|||
|
|
{
|
|||
|
|
// 取消订阅
|
|||
|
|
var themeService = Services.GetService<IThemeService>();
|
|||
|
|
if (themeService != null)
|
|||
|
|
{
|
|||
|
|
themeService.ThemeChanged -= OnThemeChanged;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
base.Dispose();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🖼️ 图片与图标适配
|
|||
|
|
|
|||
|
|
### 图标适配方案
|
|||
|
|
|
|||
|
|
#### 方案 1: 使用 Emoji(推荐)
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- Emoji 自动适配主题 -->
|
|||
|
|
<TextBlock Text="☀️" FontSize="48"/>
|
|||
|
|
<TextBlock Text="🌙" FontSize="48"/>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- ✅ 无需额外资源
|
|||
|
|
- ✅ 自动适配主题
|
|||
|
|
- ✅ 跨平台显示一致
|
|||
|
|
|
|||
|
|
#### 方案 2: 使用颜色可变的图标
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- Path 图标,颜色跟随主题 -->
|
|||
|
|
<Path Data="M12 2L2 7l10 5 10-5-10-5z..."
|
|||
|
|
Fill="{DynamicResource TextFillColorPrimaryBrush}"
|
|||
|
|
Width="24"
|
|||
|
|
Height="24"/>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**优点**:
|
|||
|
|
- ✅ 完美适配主题
|
|||
|
|
- ✅ 矢量图形,清晰度高
|
|||
|
|
- ✅ 可自定义样式
|
|||
|
|
|
|||
|
|
#### 方案 3: 提供两套图片
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class IconHelper
|
|||
|
|
{
|
|||
|
|
public static string GetThemedIcon(string iconName, Theme theme)
|
|||
|
|
{
|
|||
|
|
if (theme == Theme.Dark)
|
|||
|
|
{
|
|||
|
|
return $"avares://MyPlugin/Assets/Icons/Dark/{iconName}.png";
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
return $"avares://MyPlugin/Assets/Icons/Light/{iconName}.png";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<Image Source="{Binding ThemedIconPath}"
|
|||
|
|
Width="24"
|
|||
|
|
Height="24"/>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**目录结构**:
|
|||
|
|
```
|
|||
|
|
Assets/
|
|||
|
|
├── Icons/
|
|||
|
|
│ ├── Light/
|
|||
|
|
│ │ ├── weather.png
|
|||
|
|
│ │ └── settings.png
|
|||
|
|
│ └── Dark/
|
|||
|
|
│ ├── weather.png
|
|||
|
|
│ └── settings.png
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 图片适配示例
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class WeatherComponent : ComponentBase
|
|||
|
|
{
|
|||
|
|
private string _weatherIconPath = "";
|
|||
|
|
|
|||
|
|
public string WeatherIconPath
|
|||
|
|
{
|
|||
|
|
get => _weatherIconPath;
|
|||
|
|
set => SetProperty(ref _weatherIconPath, value);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override async Task InitializeAsync()
|
|||
|
|
{
|
|||
|
|
// 初始化图标
|
|||
|
|
UpdateWeatherIcon();
|
|||
|
|
|
|||
|
|
// 订阅主题变更
|
|||
|
|
var themeService = Services.GetService<IThemeService>();
|
|||
|
|
if (themeService != null)
|
|||
|
|
{
|
|||
|
|
themeService.ThemeChanged += (s, e) => UpdateWeatherIcon();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void UpdateWeatherIcon()
|
|||
|
|
{
|
|||
|
|
var themeService = Services.GetService<IThemeService>();
|
|||
|
|
var currentTheme = themeService?.CurrentTheme ?? Theme.Light;
|
|||
|
|
|
|||
|
|
var themePath = currentTheme == Theme.Dark ? "Dark" : "Light";
|
|||
|
|
WeatherIconPath = $"avares://MyPlugin/Assets/Icons/{themePath}/sunny.png";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🎨 自定义主题
|
|||
|
|
|
|||
|
|
### 扩展主题系统
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class CustomTheme
|
|||
|
|
{
|
|||
|
|
public string Name { get; set; } = "";
|
|||
|
|
public Dictionary<string, Color> Colors { get; set; } = new();
|
|||
|
|
|
|||
|
|
public void Apply()
|
|||
|
|
{
|
|||
|
|
var resources = Application.Current!.Resources;
|
|||
|
|
|
|||
|
|
foreach (var (key, color) in Colors)
|
|||
|
|
{
|
|||
|
|
resources[key] = new SolidColorBrush(color);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用自定义主题
|
|||
|
|
var customTheme = new CustomTheme
|
|||
|
|
{
|
|||
|
|
Name = "Ocean Blue",
|
|||
|
|
Colors = new Dictionary<string, Color>
|
|||
|
|
{
|
|||
|
|
["CardBackgroundBrush"] = Color.FromRgb(230, 240, 255),
|
|||
|
|
["AccentBrush"] = Color.FromRgb(0, 120, 215),
|
|||
|
|
["TextFillColorPrimaryBrush"] = Color.FromRgb(28, 28, 28)
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
customTheme.Apply();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 用户自定义颜色
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class ThemeCustomizationService
|
|||
|
|
{
|
|||
|
|
public void SetCustomAccentColor(Color color)
|
|||
|
|
{
|
|||
|
|
var resources = Application.Current!.Resources;
|
|||
|
|
|
|||
|
|
// 更新强调色
|
|||
|
|
resources["AccentBrush"] = new SolidColorBrush(color);
|
|||
|
|
|
|||
|
|
// 自动生成悬停和按下颜色
|
|||
|
|
var hoverColor = DarkenColor(color, 0.1);
|
|||
|
|
var pressedColor = DarkenColor(color, 0.2);
|
|||
|
|
|
|||
|
|
resources["AccentHoverBrush"] = new SolidColorBrush(hoverColor);
|
|||
|
|
resources["AccentPressedBrush"] = new SolidColorBrush(pressedColor);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private Color DarkenColor(Color color, double factor)
|
|||
|
|
{
|
|||
|
|
return Color.FromRgb(
|
|||
|
|
(byte)(color.R * (1 - factor)),
|
|||
|
|
(byte)(color.G * (1 - factor)),
|
|||
|
|
(byte)(color.B * (1 - factor))
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 🔍 主题测试
|
|||
|
|
|
|||
|
|
### 测试清单
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class ThemeTestHelper
|
|||
|
|
{
|
|||
|
|
public static async Task<List<string>> ValidateThemeSupport(Control component)
|
|||
|
|
{
|
|||
|
|
var issues = new List<string>();
|
|||
|
|
|
|||
|
|
// 测试亮色主题
|
|||
|
|
SwitchTheme(Theme.Light);
|
|||
|
|
await Task.Delay(100);
|
|||
|
|
issues.AddRange(CheckContrast(component, Theme.Light));
|
|||
|
|
|
|||
|
|
// 测试暗色主题
|
|||
|
|
SwitchTheme(Theme.Dark);
|
|||
|
|
await Task.Delay(100);
|
|||
|
|
issues.AddRange(CheckContrast(component, Theme.Dark));
|
|||
|
|
|
|||
|
|
return issues;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static List<string> CheckContrast(Control component, Theme theme)
|
|||
|
|
{
|
|||
|
|
var issues = new List<string>();
|
|||
|
|
|
|||
|
|
// 检查文本对比度
|
|||
|
|
var textBlocks = component.GetVisualDescendants()
|
|||
|
|
.OfType<TextBlock>();
|
|||
|
|
|
|||
|
|
foreach (var textBlock in textBlocks)
|
|||
|
|
{
|
|||
|
|
var foreground = GetColor(textBlock.Foreground);
|
|||
|
|
var background = GetBackgroundColor(textBlock);
|
|||
|
|
|
|||
|
|
var contrast = CalculateContrast(foreground, background);
|
|||
|
|
if (contrast < 4.5)
|
|||
|
|
{
|
|||
|
|
issues.Add($"Low contrast in {theme} theme: {contrast:F2}:1");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return issues;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static double CalculateContrast(Color fg, Color bg)
|
|||
|
|
{
|
|||
|
|
var l1 = GetRelativeLuminance(fg);
|
|||
|
|
var l2 = GetRelativeLuminance(bg);
|
|||
|
|
|
|||
|
|
var lighter = Math.Max(l1, l2);
|
|||
|
|
var darker = Math.Min(l1, l2);
|
|||
|
|
|
|||
|
|
return (lighter + 0.05) / (darker + 0.05);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static double GetRelativeLuminance(Color color)
|
|||
|
|
{
|
|||
|
|
var r = GetLuminanceComponent(color.R / 255.0);
|
|||
|
|
var g = GetLuminanceComponent(color.G / 255.0);
|
|||
|
|
var b = GetLuminanceComponent(color.B / 255.0);
|
|||
|
|
|
|||
|
|
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static double GetLuminanceComponent(double c)
|
|||
|
|
{
|
|||
|
|
return c <= 0.03928 ? c / 12.92 : Math.Pow((c + 0.055) / 1.055, 2.4);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## ✅ 主题适配检查清单
|
|||
|
|
|
|||
|
|
发布前请确保:
|
|||
|
|
|
|||
|
|
### 颜色资源
|
|||
|
|
- [ ] 所有颜色使用 `DynamicResource`
|
|||
|
|
- [ ] 没有硬编码的颜色值
|
|||
|
|
- [ ] 使用系统提供的颜色资源
|
|||
|
|
- [ ] 自定义颜色定义了亮色和暗色两个版本
|
|||
|
|
|
|||
|
|
### 文本对比度
|
|||
|
|
- [ ] 亮色主题下文本对比度 ≥ 4.5:1
|
|||
|
|
- [ ] 暗色主题下文本对比度 ≥ 4.5:1
|
|||
|
|
- [ ] 大号文本对比度 ≥ 3:1
|
|||
|
|
- [ ] UI 元素对比度 ≥ 3:1
|
|||
|
|
|
|||
|
|
### 图标与图片
|
|||
|
|
- [ ] 图标适配亮色主题
|
|||
|
|
- [ ] 图标适配暗色主题
|
|||
|
|
- [ ] 图片在两种主题下都清晰可见
|
|||
|
|
- [ ] 没有使用会"消失"的白色/黑色图标
|
|||
|
|
|
|||
|
|
### 交互状态
|
|||
|
|
- [ ] 悬停状态在两种主题下都清晰
|
|||
|
|
- [ ] 按下状态在两种主题下都清晰
|
|||
|
|
- [ ] 聚焦状态在两种主题下都清晰
|
|||
|
|
- [ ] 禁用状态在两种主题下都清晰
|
|||
|
|
|
|||
|
|
### 实际测试
|
|||
|
|
- [ ] 在亮色主题下运行并检查
|
|||
|
|
- [ ] 在暗色主题下运行并检查
|
|||
|
|
- [ ] 切换主题时无闪烁或错误
|
|||
|
|
- [ ] 长时间使用眼睛舒适
|
|||
|
|
|
|||
|
|
## 🎓 最佳实践
|
|||
|
|
|
|||
|
|
### DO - 应该这样做
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- ✅ 使用 DynamicResource -->
|
|||
|
|
<Border Background="{DynamicResource CardBackgroundBrush}">
|
|||
|
|
<TextBlock Foreground="{DynamicResource TextFillColorPrimaryBrush}"/>
|
|||
|
|
</Border>
|
|||
|
|
|
|||
|
|
<!-- ✅ 使用语义化的资源名称 -->
|
|||
|
|
<Button Background="{DynamicResource AccentBrush}"/>
|
|||
|
|
<TextBlock Foreground="{DynamicResource TextFillColorSecondaryBrush}"/>
|
|||
|
|
|
|||
|
|
<!-- ✅ 订阅主题变更事件 -->
|
|||
|
|
themeService.ThemeChanged += OnThemeChanged;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### DON'T - 不应该这样做
|
|||
|
|
|
|||
|
|
```xml
|
|||
|
|
<!-- ❌ 硬编码颜色 -->
|
|||
|
|
<Border Background="#FFFFFF">
|
|||
|
|
<TextBlock Foreground="#000000"/>
|
|||
|
|
</Border>
|
|||
|
|
|
|||
|
|
<!-- ❌ 使用 StaticResource -->
|
|||
|
|
<Border Background="{StaticResource CardBackgroundBrush}">
|
|||
|
|
<!-- 不会响应主题切换 -->
|
|||
|
|
</Border>
|
|||
|
|
|
|||
|
|
<!-- ❌ 假设总是亮色主题 -->
|
|||
|
|
<Image Source="avares://MyPlugin/Assets/white-icon.png"/>
|
|||
|
|
<!-- 在暗色主题下看不见 -->
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 📖 相关文档
|
|||
|
|
|
|||
|
|
- [视觉规范](02-视觉规范.md) - 完整的颜色系统
|
|||
|
|
- [布局规范](03-布局规范.md) - 安全区域和间距
|
|||
|
|
- [交互规范](04-交互规范.md) - 交互状态和动画
|
|||
|
|
- [组件系统](../01-插件开发/02-核心概念/02-组件系统.md) - 组件开发
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**记住**: 使用 DynamicResource,测试两种主题,确保对比度,适配图标图片。
|