【问题标题】:Brush to Brush Animation画笔到画笔动画
【发布时间】:2017-02-23 18:13:37
【问题描述】:

我设法找到了如何制作 WPF 动画 - 两种颜色之间的过渡。

它叫做 ColorAnimation,效果很好。

ColorAnimation animation = new ColorAnimation
{
    From = Colors.DarkGreen,
    To = Colors.Transparent,
    Duration = new Duration(TimeSpan.FromSeconds(1.5)),
    AutoReverse = false
};
animation.Completed += new EventHandler(animation_Completed);
SolidColorBrush brush = new SolidColorBrush(Colors.Transparent);
animation.AccelerationRatio = 0.5;

Background = brush;
brush.BeginAnimation(SolidColorBrush.ColorProperty, animation);

我正在使用它来为我的用户控件的背景设置动画。我的控件背景是SolidColorBrush。最近我换成了LinearGradientBrush。现在我不能再使用我的动画了。

我需要从画笔到画笔的动画,而不是从颜色到颜色的动画。最好的选择是抽象画笔类型,它包括 SolidColor、LinearGradient 等,所以我可以从SolidColorBrushLinearGradientBrush 进行动画处理。这甚至可能吗?谢谢。

【问题讨论】:

    标签: c# wpf animation brush


    【解决方案1】:

    另一种可能的方法是创建一个自定义动画类来为画笔设置动画。 我找到了一种简单的方法,方法是创建一个派生自AnimationTimeline 的类。我们可以覆盖自定义类中的一些成员,其中包括AnimationTimeline.GetCurrentValue method。它返回一个取决于动画进度以及开始和结束值的值。

    最简单的方法是创建一个VisualBrush 并使用子控件上的Opacity 属性交叉淡入淡出开始值和结束值。结果是一个类似下面的类:

    public class BrushAnimation : AnimationTimeline
    {
        public override Type TargetPropertyType
        {
            get
            {
                return typeof(Brush);
            }
        }
    
        public override object GetCurrentValue(object defaultOriginValue,
                                               object defaultDestinationValue,
                                               AnimationClock animationClock)
        {
            return GetCurrentValue(defaultOriginValue as Brush,
                                   defaultDestinationValue as Brush,
                                   animationClock);
        }
        public object GetCurrentValue(Brush defaultOriginValue,
                                      Brush defaultDestinationValue,
                                      AnimationClock animationClock)
        {
            if (!animationClock.CurrentProgress.HasValue)
                return Brushes.Transparent;
    
            //use the standard values if From and To are not set 
            //(it is the value of the given property)
            defaultOriginValue = this.From ?? defaultOriginValue;
            defaultDestinationValue = this.To ?? defaultDestinationValue;
    
            if (animationClock.CurrentProgress.Value == 0)
                return defaultOriginValue;
            if (animationClock.CurrentProgress.Value == 1)
                return defaultDestinationValue;
    
            return new VisualBrush(new Border()
            {
                Width = 1,
                Height = 1,
                Background = defaultOriginValue,
                Child = new Border()
                {
                    Background = defaultDestinationValue,
                    Opacity = animationClock.CurrentProgress.Value,
                }
            });
        }
    
        protected override Freezable CreateInstanceCore()
        {
            return new BrushAnimation();
        }
    
        //we must define From and To, AnimationTimeline does not have this properties
        public Brush From
        {
            get { return (Brush)GetValue(FromProperty); }
            set { SetValue(FromProperty, value); }
        }
        public Brush To
        {
            get { return (Brush)GetValue(ToProperty); }
            set { SetValue(ToProperty, value); }
        }
    
        public static readonly DependencyProperty FromProperty =
            DependencyProperty.Register("From", typeof(Brush), typeof(BrushAnimation));
        public static readonly DependencyProperty ToProperty =
            DependencyProperty.Register("To", typeof(Brush), typeof(BrushAnimation));
    }
    

    您可以像往常一样在 XAML 中使用它:

    <EventTrigger RoutedEvent="Loaded">
        <BeginStoryboard>
            <Storyboard >
                <local:BrushAnimation Storyboard.TargetName="border"
                                      Storyboard.TargetProperty="Background" 
                                      Duration="0:0:5" From="Red" 
                                      RepeatBehavior="Forever" AutoReverse="True" >
                    <local:BrushAnimation.To>
                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                            <GradientStop Color="#FF00FF2E" Offset="0.005"/>
                            <GradientStop Color="#FFC5FF00" Offset="1"/>
                            <GradientStop Color="Blue" Offset="0.43"/>
                        </LinearGradientBrush>
                    </local:BrushAnimation.To>
                </local:BrushAnimation>
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
    

    或在后面的代码中:

    var animation = new BrushAnimation
    {
        From = Brushes.Red,
        To = new LinearGradientBrush (Colors.Green, Colors.Yellow, 45),
        Duration = new Duration(TimeSpan.FromSeconds(5)),
    };
    animation.Completed += new EventHandler(animation_Completed);
    Storyboard.SetTarget(animation, border);
    Storyboard.SetTargetProperty(animation, new PropertyPath("Background"));
    
    var sb = new Storyboard();
    sb.Children.Add(animation);
    sb.Begin();
    

    还可以使用构造函数重载等来扩展BrushAnimation,因此它看起来像一个 .NET 给定的动画类型。

    【讨论】:

    • 我喜欢这个外观 - 我会试一试。
    • 两年后,这真是太棒了。
    • 两年后,再加上一点,我发现每次在GetCurrentValue 中创建的new VisualBrush 确实会导致内存泄漏(我责怪.NET,而不是你的代码!)。我修改了该类以维护一个 VisualBrush(称为“WorkingBrush”),就像您在那里创建的一样,但随后只需按如下方式更改不透明度:((WorkingBrush.Visual as Border).Child as Border).Opacity = _clock.CurrentProgress.Value;
    • 应该标记为答案;这是一个完美的解决方案
    • 这可能会更慢,但这种性能影响通常可以忽略不计。我喜欢这个解决方案
    【解决方案2】:

    您只需要在渐变画笔的渐变色块上使用颜色动画即可。这是一个使用故事板为矩形渐变设置动画的示例。

        <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        x:Class="GradientBrushAnimation.MainWindow"
        x:Name="Window"
        Title="MainWindow"
        Width="640" Height="480">
        <Window.Resources>
            <Storyboard x:Key="Storyboard1">
                <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="rectangle">
                    <EasingColorKeyFrame KeyTime="0:0:2" Value="Red"/>
                </ColorAnimationUsingKeyFrames>
                <ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="rectangle">
                    <EasingColorKeyFrame KeyTime="0:0:2" Value="#FF71FF00"/>
                </ColorAnimationUsingKeyFrames>
            </Storyboard>
        </Window.Resources>
        <Window.Triggers>
            <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                <BeginStoryboard Storyboard="{StaticResource Storyboard1}"/>
            </EventTrigger>
        </Window.Triggers>
    
        <Grid x:Name="LayoutRoot">
            <Rectangle x:Name="rectangle" Margin="78,102,292,144" Stroke="Black">
                <Rectangle.Fill>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="Black" Offset="0"/>
                        <GradientStop Color="White" Offset="1"/>
                    </LinearGradientBrush>
                </Rectangle.Fill>
            </Rectangle>
        </Grid>
    </Window>
    

    【讨论】:

    • 如果我知道应该使用哪种类型的刷子,那效果会很好。但在我的情况下,可以有纯色、线性渐变或径向渐变,具体取决于用户设置。有没有通用的刷子动画方式(与刷子类型无关)?
    【解决方案3】:

    如果您有一个模板样式,您可以在其中为填充画笔命名,您可以为画笔的颜色设置动画,如下所示:

    <Rectangle Width="100" Height="100">
      <Rectangle.Fill>
        <SolidColorBrush x:Name="MyAnimatedBrush" Color="Orange" />
      </Rectangle.Fill>
      <Rectangle.Triggers>
    
        <!-- Animates the brush's color to gray
             When the mouse enters the rectangle. -->
        <EventTrigger RoutedEvent="Rectangle.MouseEnter">
          <BeginStoryboard>
            <Storyboard>
              <ColorAnimation
                Storyboard.TargetName="MyAnimatedBrush"
                Storyboard.TargetProperty="Color"
                To="Gray" Duration="0:0:1" />
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger>  
      </Rectangle.Triggers>
    </Rectangle>
    

    取自MSDN

    【讨论】:

    • 这仅适用于纯色画笔 - 这不适用于渐变画笔。