137 lines
5.1 KiB
C#
137 lines
5.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using System.Windows;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
|
|
namespace Sample
|
|
{
|
|
public enum ThemeMode { Light, Dark }
|
|
|
|
/// <summary>
|
|
/// 一个优雅且绝对可靠的主题管理器。
|
|
/// 它通过一个明确的资源键列表,精确地对指定画刷进行平滑的过渡动画。
|
|
/// </summary>
|
|
public static class HybridThemeManager
|
|
{
|
|
// 【契约】我们回归到使用一个明确的、硬编码的列表。
|
|
// 这是最可靠的方式,它清晰地定义了哪些资源参与动画。
|
|
private static readonly List<string> AnimatableBrushKeys = new List<string>
|
|
{
|
|
//"AppBackgroundBrush",
|
|
//"AppTextBrush"
|
|
};
|
|
static HybridThemeManager()
|
|
{
|
|
var appResources = Application.Current.Resources;
|
|
foreach (var dict in appResources.MergedDictionaries)
|
|
{
|
|
var keys = dict.Keys;
|
|
//AnimatableBrushKeys.Clear();
|
|
AnimatableBrushKeys.AddRange(
|
|
keys.OfType<string>()
|
|
.Where(key => key.EndsWith("Brush") && dict[key] is SolidColorBrush)
|
|
);
|
|
}
|
|
//foreach (ResourceDictionary dict in appResources)
|
|
//{
|
|
|
|
//}
|
|
}
|
|
private static ThemeMode _currentMode = ThemeMode.Light;
|
|
private static bool _isSwitching = false;
|
|
|
|
public static async Task SwitchThemeAsync()
|
|
{
|
|
if (_isSwitching) return;
|
|
|
|
_isSwitching = true;
|
|
try
|
|
{
|
|
var targetMode = _currentMode == ThemeMode.Light ? ThemeMode.Dark : ThemeMode.Light;
|
|
var appResources = Application.Current.Resources;
|
|
|
|
// 步骤 1: “快照” - 根据【明确的列表】,记录当前颜色
|
|
var fromColors = TakeColorSnapshot(appResources);
|
|
|
|
// 步骤 2: “交换” - 替换资源字典
|
|
SwapThemeDictionary(appResources, targetMode);
|
|
|
|
// 步骤 3: “动画” - 对【明确的列表】中的画刷启动动画
|
|
await AnimateToNewTheme(appResources, fromColors);
|
|
|
|
_currentMode = targetMode;
|
|
}
|
|
finally
|
|
{
|
|
_isSwitching = false;
|
|
}
|
|
}
|
|
|
|
private static Dictionary<string, Color> TakeColorSnapshot(ResourceDictionary resources)
|
|
{
|
|
return AnimatableBrushKeys
|
|
.Where(key => resources.Contains(key) && resources[key] is SolidColorBrush)
|
|
.ToDictionary(key => key, key => ((SolidColorBrush)resources[key]).Color);
|
|
}
|
|
|
|
private static void SwapThemeDictionary(ResourceDictionary resources, ThemeMode targetMode)
|
|
{
|
|
var newThemeUri = new Uri($"/Themes/{targetMode}.xaml", UriKind.Relative);
|
|
var newThemeDict = new ResourceDictionary { Source = newThemeUri };
|
|
|
|
var oldDict = resources.MergedDictionaries.FirstOrDefault(d => d.Source?.OriginalString.Contains("/Themes/") ?? false);
|
|
if (oldDict != null)
|
|
{
|
|
resources.MergedDictionaries.Remove(oldDict);
|
|
}
|
|
resources.MergedDictionaries.Add(newThemeDict);
|
|
}
|
|
|
|
private static Task AnimateToNewTheme(ResourceDictionary resources, IReadOnlyDictionary<string, Color> fromColors)
|
|
{
|
|
var animationTasks = new List<Task>();
|
|
foreach (var key in AnimatableBrushKeys)
|
|
{
|
|
if (resources.Contains(key) && resources[key] is SolidColorBrush newBrush && fromColors.TryGetValue(key, out var fromColor))
|
|
{
|
|
// 【健壮性】依然保留对 StaticResource (冻结) 的处理
|
|
if (newBrush.IsFrozen)
|
|
{
|
|
newBrush = newBrush.Clone();
|
|
resources[key] = newBrush;
|
|
}
|
|
|
|
var toColor = newBrush.Color;
|
|
newBrush.Color = fromColor; // 伪装
|
|
|
|
animationTasks.Add(AnimateBrushColorAsync(newBrush, toColor));
|
|
}
|
|
}
|
|
return Task.WhenAll(animationTasks);
|
|
}
|
|
|
|
private static Task AnimateBrushColorAsync(SolidColorBrush brush, Color toColor)
|
|
{
|
|
var tcs = new TaskCompletionSource<bool>();
|
|
var animation = new ColorAnimation
|
|
{
|
|
From = brush.Color,
|
|
To = toColor,
|
|
Duration = TimeSpan.FromMilliseconds(400),
|
|
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseOut }
|
|
};
|
|
|
|
animation.Completed += (s, e) =>
|
|
{
|
|
brush.BeginAnimation(SolidColorBrush.ColorProperty, null);
|
|
brush.Color = toColor;
|
|
tcs.TrySetResult(true);
|
|
};
|
|
brush.BeginAnimation(SolidColorBrush.ColorProperty, animation);
|
|
return tcs.Task;
|
|
}
|
|
}
|
|
} |