You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#自定义WPF ColorAnimation:实现用户自定义颜色绑定及效果优化

WPF颜色动画To属性绑定自定义颜色的优化方案

问题描述

我在WPF资源字典里有大量带颜色动画故事板的样式和控件模板,需要把这些颜色动画的To属性绑定到用户选择的任意颜色。之前找到的方案只适用于Double动画,于是自己写了一段自定义颜色动画的代码,但效果不如WPF原生颜色动画,想找更优实现或让自定义效果贴近原生的方法。

用户自定义实现代码:

SolidColorBrush brush = new SolidColorBrush(System.Windows.Media.Color.FromRgb(0,0,0));
if (values[1] is string && values[2] is string && values[0] is double)
{
     System.Drawing.Color color = ColorTranslator.FromHtml(values[1].ToString());
     double r = System.Convert.ToInt16(color.R);
     double g = System.Convert.ToInt16(color.G);
     double b = System.Convert.ToInt16(color.B);
     System.Drawing.Color color2 = ColorTranslator.FromHtml(values[2].ToString());
     double r2 = System.Convert.ToInt16(color2.R);
     double g2 = System.Convert.ToInt16(color2.G);
     double b2 = System.Convert.ToInt16(color2.B);
     int r3 = System.Convert.ToInt32(r + ((r2 - r) * (double)values[0]));
     int g3 = System.Convert.ToInt32(g + ((g2 - g) * (double)values[0]));
     int b3 = System.Convert.ToInt32(b + ((b2 - b) * (double)values[0]));
     brush = new SolidColorBrush(System.Windows.Media.Color.FromRgb(System.Convert.ToByte(r3), System.Convert.ToByte(g3), System.Convert.ToByte(b3)));
}
            
return brush;

优化方案

1. 直接用ColorAnimationUsingKeyFrames实现绑定(推荐)

原生ColorAnimation不支持直接绑定To属性,但ColorAnimationUsingKeyFrames可以通过关键帧绑定动态颜色,完全复用WPF原生插值逻辑,效果和原生动画一致:

<Storyboard>
    <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)"
                                  Duration="0:0:0.3">
        <!-- 起始颜色可以绑定静态资源或初始值 -->
        <LinearColorKeyFrame KeyTime="0:0:0" Value="{StaticResource DefaultBackgroundColor}"/>
        <!-- 目标颜色绑定用户选择的颜色 -->
        <LinearColorKeyFrame KeyTime="0:0:0.3" Value="{Binding UserSelectedColor}"/>
    </ColorAnimationUsingKeyFrames>
</Storyboard>

2. 修复自定义动画的色彩空间插值逻辑

你的自定义实现效果差的核心原因是直接对RGB分量做线性插值,而WPF原生动画是在sRGB色彩空间(带伽马校正)插值。优化后代码如下:

// sRGB转线性空间(伽马校正)
private double SrgbToLinear(byte component)
{
    double c = component / 255.0;
    return c <= 0.04045 ? c / 12.92 : Math.Pow((c + 0.055) / 1.055, 2.4);
}

// 线性空间转回sRGB
private byte LinearToSrgb(double value)
{
    double c = value <= 0.0031308 ? value * 12.92 : 1.055 * Math.Pow(value, 1/2.4) - 0.055;
    return (byte)Math.Clamp(Math.Round(c * 255), 0, 255);
}

// 优化后的插值方法
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
    if (values[0] is double progress && values[1] is string startColorStr && values[2] is string endColorStr)
    {
        var startColor = System.Drawing.ColorTranslator.FromHtml(startColorStr);
        var endColor = System.Drawing.ColorTranslator.FromHtml(endColorStr);

        // 转线性空间插值
        double rLinear = SrgbToLinear(startColor.R) + (SrgbToLinear(endColor.R) - SrgbToLinear(startColor.R)) * progress;
        double gLinear = SrgbToLinear(startColor.G) + (SrgbToLinear(endColor.G) - SrgbToLinear(startColor.G)) * progress;
        double bLinear = SrgbToLinear(startColor.B) + (SrgbToLinear(endColor.B) - SrgbToLinear(startColor.B)) * progress;

        // 转回sRGB
        byte r = LinearToSrgb(rLinear);
        byte g = LinearToSrgb(gLinear);
        byte b = LinearToSrgb(bLinear);

        return new SolidColorBrush(System.Windows.Media.Color.FromRgb(r, g, b));
    }
    return new SolidColorBrush(System.Windows.Media.Colors.Black);
}

3. 自定义可绑定的AnimationTimeline

继承AnimationTimeline实现支持绑定的颜色动画,语法和原生动画一致,同时保留原生插值效果:

public class BindableColorAnimation : AnimationTimeline
{
    public static readonly DependencyProperty FromProperty =
        DependencyProperty.Register("From", typeof(Color?), typeof(BindableColorAnimation));

    public static readonly DependencyProperty ToProperty =
        DependencyProperty.Register("To", typeof(Color?), typeof(BindableColorAnimation));

    public Color? From
    {
        get => (Color?)GetValue(FromProperty);
        set => SetValue(FromProperty, value);
    }

    public Color? To
    {
        get => (Color?)GetValue(ToProperty);
        set => SetValue(ToProperty, value);
    }

    public override Type TargetPropertyType => typeof(Color);

    protected override Freezable CreateInstanceCore() => new BindableColorAnimation();

    public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
    {
        var progress = animationClock.CurrentProgress.Value;
        var from = From ?? (Color)defaultOriginValue;
        var to = To ?? (Color)defaultDestinationValue;

        // 复用WPF原生颜色插值逻辑
        return Color.FromArgb(
            (byte)(from.A + (to.A - from.A) * progress),
            (byte)(from.R + (to.R - from.R) * progress),
            (byte)(from.G + (to.G - from.G) * progress),
            (byte)(from.B + (to.B - from.B) * progress)
        );
    }
}

XAML使用示例:

<local:BindableColorAnimation Storyboard.TargetProperty="(Foreground).(SolidColorBrush.Color)"
                              From="#FF333333"
                              To="{Binding UserSelectedTextColor}"
                              Duration="0:0:0.2"/>

内容的提问来源于stack exchange,提问作者megahendrik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 05:07:10