首页 > 代码库 > WPF 有趣的动画效果

WPF 有趣的动画效果

WPF 有趣的动画效果

        这一次我要呈上一个简单的文章,关于给你的WPF apps加入美丽的光线动画,可是我对动画这东西可能有点入迷了。

        实际上。我对动画如此的入迷,以至于我最后做了之前从未打算做的东西,就是使用一些很实用的.NET代码,渐变填充生成背景动画。让我先给你看一些终于效果吧。

技术分享
WPF和元素定位
        然而。在我们開始之前。我们须要考虑一件事情。这件事让我也有点原地转圈的感觉。

        似乎当你使用WPF创建不论什么闭环形状时,你不能设置它的X和Y坐标。好吧。至少你不能在一般的WPF窗口(像VS开箱即用的形状)上。
        这一点上,我要谢谢我的好朋友(以及WPF各种大神)Gavin Lanata。他帮我解释说,假设要在代码中以我想要的方式定位。就不得不将窗体的根布局从grid变为canvas。

这是非常令人沮丧的。如此简单的东西(而且是大多开发者会考虑寻找的东西)在WPF中却不被觉得是绘制形状的要求。

我跑题了。如今我们什么也做不了。

让我们開始吧
        打开Visual Studio,開始新的WPF应用程序项目。命名为WpfAnimationTest。

你能够使用Blend。可是由于我们大多数要输入代码。Blend可能有点过度了。

        一旦你的模板载入完成,请改动你的“MainWindow.xaml”文件,看起来像这样。
<Window x:Class="WpfAnimationTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="WPF Animation Testing" Height="600" Width="800"
         WindowStartupLocation="CenterScreen">
    <Canvas x:Name="Root" Background="Black">        
    </Canvas>
</Window>
        你会看到。除了将背景变为黑色,我们改变了默认的grid为canvas。而且设置了窗口大小和标题。

改变的也不多。不像大多说WPF/XAML项目,这里我们画的大部分用代码实现。

        使用这个记录。你能够轻易使用声明的XAML,复制本文中的全部东西,可是会有大量反复渐变、渐变点和其它须要资源的东西。通过使用这里的代码方法,我们是用的不论什么东西在超出范围后都能够处理掉;并且更重要的是。它是可重用代码。


首先是一点儿理论

        假设你了解过XAML,你将可能知道WPF中全部东西都能够成为动画。颜色,位置,大小。填充这些能够使用时间线和故事板能够非常easy变成动画。
        你使用很多可用动画时间线类型中的一种创建你的动画顺序。通常新建对象。然后设置開始值、终止值,以及动画执行时间长度。然后在将它们附加到故事板并启动执行之前,附加这些动画时间线到你想要它们控制的UI元素的属性上。
        然而。有件事我没有意识到,一个彩色渐变点位置也能够做成动画。当你在WPF中创建颜色渐变时。你使用一系列对象“渐变点”创建了它们。
        假设你以前使用过PhotoShop之类的图像处理软件,而且使用小方形颜色标记器在颜色应该改变的地方标记填充位置,你已经使用了相似的概念。

比如,假设你想要在中间使用从红到蓝颜色渐变,然后变绿,你可能在0%处创建红色点。在50%创建蓝色,100%处创建绿色。

WPF图画引擎然后在全部颜色间填充。这样你就得到从一个颜色到下一个的平滑过渡。

        在大多数WPF中大多数測量使用所谓的本地坐标系统。这意味着你的窗口上,可能宽度有比方800像素,从0%到100%范围使用0到1来代表。设置渐变时,这可能意味着0像素在0.0或0%,400像素在0.5或50%,800像素在1.0或100%,全部你的颜色点位置都是这样指定的。
我们试一些代码吧
        打开主要窗体XAML代码。如果你已经如之前提到的改动了canvas,你应该加入例如以下代码到MainWindow构造器中。
        public MainWindow()
        {
            InitializeComponent();
            Rectangle myRect = new Rectangle
            {
                Width = 300,
                Height = 100,
                Stroke = Brushes.White,
                StrokeThickness = 1
            };
            Root.Children.Add(myRect);
            Canvas.SetLeft(myRect, 100);
            Canvas.SetTop(myRect, 100);
        }
        当你按下F5,你应该能看到黑色背景上白色矩形,距离每一个角都是100像素。大小300像素*100像素。须要Canvas SetLeft和SetTop调用,是由于微软决定同意绘制闭环形状的不论什么人来决定形状绘制的位置时没实用处的。
        假设你也添加了填充參数:
        public MainWindow()
        {
            InitializeComponent();
            Rectangle myRect = new Rectangle
            {
                Width = 300,
                Height = 100,
                Stroke = Brushes.White,
                StrokeThickness = 1,
                Fill=Brushes.Red
            };
            Root.Children.Add(myRect);
            Canvas.SetLeft(myRect, 100);
            Canvas.SetTop(myRect, 100);
        }
        你也能够设置矩形全局的实心填充颜色。

然而。实心颜色有点无趣。

我们将改动成更加有趣的东西。

        首先,让我们通过将它在自身的函数中包装起来,然后也创建数据对象传送參数过去。我们将加入全部须要的參数到数据对象,而且我将解释我们遇到的每一个对象。
        加入新类到你的项目,命名为BarDescription。

输入例如以下代码:

namespace WpfAnimationTest
{
    public class BarDescriptor
    {
        public int RectangleX { get; set; }
        public int RectangleY { get; set; }
        public int RectangleWidth { get; set; }
        public int RectangleHeight { get; set; }
        public int AnimationTimeInSeconds { get; set; }
        // 0.0 to 1.0
        public float BarBaseRedLevel { get; set; }
        public float BarBaseGreenLevel { get; set; }
        public float BarBaseBlueLevel { get; set; }
        public float GradientStartX { get; set; }
        public float GradientStartY { get; set; }
        public float GradientEndX { get; set; }
        public float GradientEndY { get; set; }
    }
}
        确保依照你的须要改动了命名空间。

        然后,打开MainWindow.xaml.cs(或者你随意命名的主窗体后台代码)。然后确保输入一下代码:
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
 
namespace WpfAnimationTest
{
   public partial class MainWindow
   {
      private Rectangle _testRect;
 
      public MainWindow()
      {
         InitializeComponent();
         Loaded += MainWindowLoaded;
      }
 
      private void MainWindowLoaded(object sender,
         RoutedEventArgs e)
      {
         BarDescriptor barOne = new BarDescriptor
         {
            RectangleX = 100,
            RectangleY = 100,
            RectangleWidth = 200,
            RectangleHeight = 200,
            AnimationTimeInSeconds = 0,
            BarBaseRedLevel = 0,
            BarBaseGreenLevel = 0,
            BarBaseBlueLevel = 0,
            GradientStartX = 0,
            GradientStartY = 0,
            GradientEndX = 0,
            GradientEndY = 0
         };
 
         CreateRectangleAnimatedRectangle(barOne);
      }
 
      private void CreateRectangleAnimatedRectangle(BarDescriptor
         inputParameters)
      {
         _testRect = new Rectangle
         {
            Width = inputParameters.RectangleWidth,
            Height = inputParameters.RectangleHeight,
            Stroke = Brushes.White,
            StrokeThickness = 1,
         };
 
         Root.Children.Add(_testRect);
         Canvas.SetLeft(_testRect, inputParameters.RectangleX);
         Canvas.SetTop(_testRect, inputParameters.RectangleY);
 
      }
 
   }
}
        再一次。确保输入正确的命名空间。正如之前的样例,执行时你应该又看到白色矩形。可是如今你也应该可以轻松加入新矩形。通过创建新的“BarDescriptor”对象,并设置对应參数。
        然而,如今如果你设置主窗体大小和我的同样(800*600)。设置barOne属性例如以下:
            BarDescriptor barOne = new BarDescriptor
            {
                RectangleX = 0,
                RectangleY = 0,
                RectangleWidth = 800,
                RectangleHeight = 600,
                AnimationTimeInSeconds = 0,
                BarBaseRedLevel = 0,
                BarBaseGreenLevel = 0,
                BarBaseBlueLevel = 0,
                GradientStartX = 0,
                GradientStartY = 0,
                GradientEndX = 0,
                GradientEndY = 0
            };
        这样会创建和主窗体相同大小的矩形。
加入渐变
        为了使用普通C#代码创建一个渐变色,你须要使用一个“LinearGradientBrush”对象和一个"GradientStopCollection"。渐变笔刷将被用来填充矩形背景,而且点集合将设置渐变过程的颜色。
        我们的渐变中。也会使用透明度。透明度从0.0到1.0-0%到100%。设置为0%意味着不同看透它;它没有透明度,而100%(1)则全然透明,显示了它背后的一切。

透明度的设置同意你控制多少背景、多少颜色显示出来。

        在创建矩形方法内部的前面,加入例如以下的代码创建你的颜色点:
            GradientStopCollection gradientStops = new GradientStopCollection
            {
                new GradientStop(Color.FromScRgb(0.0f, 1, 1, 1), 0.0),
                new GradientStop(Color.FromScRgb(0.0f, 1, 1, 1), 0.01),
                new GradientStop(Color.FromScRgb(0.5f, 1, 1, 1), 0.02),
                new GradientStop(Color.FromScRgb(1.0f, 1, 1, 1), 0.03),
                new GradientStop(Color.FromScRgb(0.5f, 1, 1, 1), 0.04),
                new GradientStop(Color.FromScRgb(0.0f, 1, 1, 1), 0.05),
                new GradientStop(Color.FromScRgb(0.0f, 1, 1, 1), 1.0),
            };
        这一系列的渐变点创建了一系列七色点全白(R/G/B值都为1),颜色点被设置沿着渐变填充长度 位置为0, 0.01, 0.02, 0.03, 0.04, 0.05,和1。而且有透明度0, 0, 0.5, 1, 0.5, 0 和0。这系列填充意味着我们从第一个点到第二个点透明,然后突然从全然透明升级为实心白色,紧接着突然降回透明;最后,从点6和点7(矩形的剩余部分)是100%透明。
        你定义了这些颜色点后,加入例如以下代码:
 LinearGradientBrush gradientBrush = new LinearGradientBrush(gradientStops, new Point(0, 0.5), new Point(1, 0.5));
        这将使用点集创建实际的渐变。它将横跨Y坐标系,沿着X轴从0到100%(1)水平执行。

一旦你添加了渐变。改动了矩形创建參数来设置渐变,当你这样做了。也会使得边界消失:

            _testRect = new Rectangle
            {
                Width = inputParameters.RectangleWidth,
                Height = inputParameters.RectangleHeight,
                Stroke = Brushes.Transparent,
                StrokeThickness = 0,
                Fill = gradientBrush
            };
        这些都做好后,执行你的应用程序,你会得到:
技术分享
        你应该可以直接看到不同透明度的影响。以及它导致填充圆柱形3D条外观的效果。为了实现动画。我们须要移动5个渐变点,组成渐变部分看起来像3D条。在上面的渐变点集中。这些值是5个值。位于两个外部值之间,位置从0.01到0.05.
        我们须要从左至右移动这些颜色点,并返回。这意味着我们须要下述动作:
  • 点1从0.01到0.95并返回;
  • 点2从0.02到0.96并返回;
  • 点3从0.03到0.97并返回。
  • 点4从0.04到0.98并返回;
  • 点5从0.05到0.99并返回。

        我们通过创建5个双值动画对象达成效果。每一个分别匹配一个颜色点。加入下述代码到矩形方法中。在创建渐变点之前:
            DoubleAnimation firstStopAnim =new DoubleAnimation(0.01, 0.95,new Duration(new TimeSpan(0, 0, 0, 5)));
            DoubleAnimation secondStopAnim = new DoubleAnimation(0.02, 0.96,new Duration(new TimeSpan(0, 0, 0, 5)));
            DoubleAnimation thirdStopAnim = new DoubleAnimation(0.03, 0.97,new Duration(new TimeSpan(0, 0, 0, 5)));
            DoubleAnimation fourthStopAnim = new DoubleAnimation(0.04, 0.98,new Duration(new TimeSpan(0, 0, 0, 5)));
            DoubleAnimation fifthStopAnim = new DoubleAnimation(0.05, 0.99,new Duration(new TimeSpan(0, 0, 0, 5)));

            firstStopAnim.AutoReverse = true;
            secondStopAnim.AutoReverse = true;
            thirdStopAnim.AutoReverse = true;
            fourthStopAnim.AutoReverse = true;
            fifthStopAnim.AutoReverse = true;

            firstStopAnim.BeginTime = new TimeSpan(0);
            secondStopAnim.BeginTime = new TimeSpan(0);
            thirdStopAnim.BeginTime = new TimeSpan(0);
            fourthStopAnim.BeginTime = new TimeSpan(0);
            fifthStopAnim.BeginTime = new TimeSpan(0);

            firstStopAnim.EasingFunction = new CubicEase();
            secondStopAnim.EasingFunction = new CubicEase();
            thirdStopAnim.EasingFunction = new CubicEase();
            fourthStopAnim.EasingFunction = new CubicEase();
            fifthStopAnim.EasingFunction = new CubicEase();
        这里我们加入5个点,然后设置默认属性,以及范围。默认属性是自己主动返回相反方向,从0s開始,使用CubicEase动画转变。

一旦我们创建动画对象。我们然后须要给内部5个渐变点以独特的名称,这样我们能够附加故事板对象控制他们的动画。

        声明渐变点集之后。创建线性渐变之前,加入例如以下代码:
            String slotOneName = RandomName();
            String slotTwoName = RandomName();
            String slotThreeName = RandomName();
            String slotFourName = RandomName();
            String slotFiveName = RandomName();

            RegisterName(slotOneName, gradientStops[1]);
            RegisterName(slotTwoName, gradientStops[2]);
            RegisterName(slotThreeName, gradientStops[3]);
            RegisterName(slotFourName, gradientStops[4]);
            RegisterName(slotFiveName, gradientStops[5]);
        函数“RandomName”是用户加入的函数,放在绘制矩形方法之后。

你须要给每一个名称一个随机名,这样你能够重用矩形绘制方法。假设你尝试重用已经被分配给之前颜色点的名称,矩形函数将终止,可是你不会得到阻止应用程序的异常。而是得到黑色窗体,没有不论什么动画。因此确保你的点名称唯一是非常重要的,可是没有混乱到你给出不可用的属性名。

我在这里使用的函数使用了随机和一个GUID结合的方式,没有危急的字符。

为了在你的代码中定义它。在MainWindow类中,矩形函数之后加入例如以下代码:

        private string RandomName()
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            const int nameLen = 8;
            var random = new Random();
            string temp = Guid.NewGuid().ToString().
               Replace("-", String.Empty);
            temp = Regex.Replace(temp, @"[\d-]",
               string.Empty).ToUpper();
            return new string(Enumerable.Repeat(chars, nameLen).Select
               (s => s[random.Next(s.Length)]).ToArray()) + temp;
        }
        随机名称设置好以后,我们然后開始在矩形函数中设置渐变动画。

下一件事是映射我们刚创建的随机属性名,附加渐变点到故事板。

我们使用WPF故事板类的静态属性来匹配,然后创建故事板局部对象,加入每一个动画到子集合。

        将以下代码放到线性渐变声明之后,可是在你建立矩形之前:
            Storyboard.SetTargetName(firstStopAnim, slotOneName);
            Storyboard.SetTargetProperty(firstStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard.SetTargetName(secondStopAnim, slotTwoName);
            Storyboard.SetTargetProperty(secondStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard.SetTargetName(thirdStopAnim, slotThreeName);
            Storyboard.SetTargetProperty(thirdStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard.SetTargetName(fourthStopAnim, slotFourName);
            Storyboard.SetTargetProperty(fourthStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard.SetTargetName(fifthStopAnim, slotFiveName);
            Storyboard.SetTargetProperty(fifthStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard gradientAnimation = new Storyboard { RepeatBehavior = RepeatBehavior.Forever };

            gradientAnimation.Children.Add(firstStopAnim);
            gradientAnimation.Children.Add(secondStopAnim);
            gradientAnimation.Children.Add(thirdStopAnim);
            gradientAnimation.Children.Add(fourthStopAnim);
            gradientAnimation.Children.Add(fifthStopAnim);
        然后紧接着加入例如以下梦幻般的代码,放置在矩形方法末尾,在设置矩形left和top之后:
gradientAnimation.Begin(this);
        假设一切正常进行。按下F5你会看到3D条左右来回移动。  
技术分享
        之前,记得我简要提到“BarDescriptor”对象的其它參数吗?我们将充分使用它。
        你已经知道。RectangleX, RectangleY, RegtangleWidth,和RectangleHeight指定位置和绘制矩形的大小。

 AnimationTimeInSeconds是你想让故事板从矩形左側到右側执行的时间。这不包括返回时间。仅仅是动画一圈的时间。 BarBaseRedLevel, BarBaseGreenLevel,和BarBaseBlueLevel被用来设置条的基色。设置这些能够用来改变条的总体颜色。

        最后,GradientStartX, GradientStartY, GradientEndX,和GradientEndY设置渐变将遵从的直线路径。这些值从0.0到1.0,代表X和Y方向的0%到100%。比如,假设设置0,0到1,1,你的3D条将从左上角到右下角,沿着对角方向动画。设置0,0.5和1,0.5将使动画沿着普通的从左到右。

        改动barOne对象,让它拥有下面值:
            BarDescriptor barOne = new BarDescriptor
            {
                RectangleX = 0,
                RectangleY = 0,
                RectangleWidth = 800,
                RectangleHeight = 600,
                AnimationTimeInSeconds = 5,
                BarBaseRedLevel = 0,
                BarBaseGreenLevel = 0.5f,
                BarBaseBlueLevel = 0,
                GradientStartX = 0,
                GradientStartY = 0.5f,
                GradientEndX = 1,
                GradientEndY = 0.5f
            };
        然后改动“CreateRectangle”方法使得參数被用在须要的地方。

改动渐变点集的创建,让它看起来例如以下:

            GradientStopCollection gradientStops = new GradientStopCollection
            {
                new GradientStop(Color.FromScRgb(0.5f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.0),
                new GradientStop(Color.FromScRgb(0.0f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.01),
                new GradientStop(Color.FromScRgb(0.5f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.02),
                new GradientStop(Color.FromScRgb(1.0f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.03),
                new GradientStop(Color.FromScRgb(0.5f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.04),
                new GradientStop(Color.FromScRgb(0.0f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.05),
                new GradientStop(Color.FromScRgb(0.5f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 1.0),
            };
        然后。改动你创建线性渐变笔刷的行,看起来例如以下:
            LinearGradientBrush gradientBrush = new LinearGradientBrush(gradientStops,new Point(inputParameters.GradientStartX,
                inputParameters.GradientStartY),new Point(inputParameters.GradientEndX,inputParameters.GradientEndY));
        假设一切顺利,你应该有一个绿色条左右移动,和之前相比有轻微渐变:
技术分享
        假设你看到上图,恭喜你。

如今须要做的就是把剩余的条加入上去,创建很多其它“BarDescriptor”对象,将它们传送到矩形创建方法例如以下:

BarDescriptor barOne = new BarDescriptor
{
   RectangleX = 0,
   RectangleY = 0,
   RectangleWidth = 800,
   RectangleHeight = 600,
   AnimationTimeInSeconds = 5,
   BarBaseRedLevel = 0,
   BarBaseGreenLevel = 0.5f,
   BarBaseBlueLevel = 0,
   GradientStartX = 0,
   GradientStartY = 0.5f,
   GradientEndX = 1,
   GradientEndY = 0.5f
};
 
BarDescriptor barTwo = new BarDescriptor
{
   RectangleX = 0,
   RectangleY = 0,
   RectangleWidth = 800,
   RectangleHeight = 600,
   AnimationTimeInSeconds = 4,
   BarBaseRedLevel = 0,
   BarBaseGreenLevel = 0.5f,
   BarBaseBlueLevel = 0,
   GradientStartX = 1,
   GradientStartY = 0,
   GradientEndX = 0,
   GradientEndY = 1
};
 
BarDescriptor barThree = new BarDescriptor
{
   RectangleX = 0,
   RectangleY = 0,
   RectangleWidth = 800,
   RectangleHeight = 600,
   AnimationTimeInSeconds = 3,
   BarBaseRedLevel = 0,
   BarBaseGreenLevel = 0.5f,
   BarBaseBlueLevel = 0,
   GradientStartX = 0,
   GradientStartY = 0,
   GradientEndX = 1,
   GradientEndY = 1
};
 
BarDescriptor barFour = new BarDescriptor
{
   RectangleX = 0,
   RectangleY = 0,
   RectangleWidth = 800,
   RectangleHeight = 600,
   AnimationTimeInSeconds = 6,
   BarBaseRedLevel = 0,
   BarBaseGreenLevel = 0.5f,
   BarBaseBlueLevel = 0,
   GradientStartX = 0.5f,
   GradientStartY = 0,
   GradientEndX = 0.5f,
   GradientEndY = 1
};
 
CreateRectangleAnimatedRectangle(barOne);
CreateRectangleAnimatedRectangle(barTwo);
CreateRectangleAnimatedRectangle(barThree);
CreateRectangleAnimatedRectangle(barFour);
        本文中开头用的图片就是四个条相互覆盖的产品,同意透明度相互结合。更改时间和更改速度有同样效果。所以不同条移动速度不一样。

        你可以试玩颜色渐变和透明度映射,并创建各种有趣的效果。

仅仅是要记住,为了可以正常执行,你必须在中心有5个渐变,集合中有5个点。

        终于完整的后台代码是这种:
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace WpfAnimationTest
{
    public partial class MainWindow
    {
        private Rectangle _testRect;

        public MainWindow()
        {
            InitializeComponent();
            Loaded += MainWindowLoaded;
        }

        private void MainWindowLoaded(object sender,
           RoutedEventArgs e)
        {
            BarDescriptor barOne = new BarDescriptor
            {
                RectangleX = 0,
                RectangleY = 0,
                RectangleWidth = 800,
                RectangleHeight = 600,
                AnimationTimeInSeconds = 5,
                BarBaseRedLevel = 0,
                BarBaseGreenLevel = 0.5f,
                BarBaseBlueLevel = 0,
                GradientStartX = 0,
                GradientStartY = 0.5f,
                GradientEndX = 1,
                GradientEndY = 0.5f
            };

            BarDescriptor barTwo = new BarDescriptor
            {
                RectangleX = 0,
                RectangleY = 0,
                RectangleWidth = 800,
                RectangleHeight = 600,
                AnimationTimeInSeconds = 4,
                BarBaseRedLevel = 0,
                BarBaseGreenLevel = 0.5f,
                BarBaseBlueLevel = 0,
                GradientStartX = 1,
                GradientStartY = 0,
                GradientEndX = 0,
                GradientEndY = 1
            };

            BarDescriptor barThree = new BarDescriptor
            {
                RectangleX = 0,
                RectangleY = 0,
                RectangleWidth = 800,
                RectangleHeight = 600,
                AnimationTimeInSeconds = 3,
                BarBaseRedLevel = 0,
                BarBaseGreenLevel = 0.5f,
                BarBaseBlueLevel = 0,
                GradientStartX = 0,
                GradientStartY = 0,
                GradientEndX = 1,
                GradientEndY = 1
            };

            BarDescriptor barFour = new BarDescriptor
            {
                RectangleX = 0,
                RectangleY = 0,
                RectangleWidth = 800,
                RectangleHeight = 600,
                AnimationTimeInSeconds = 6,
                BarBaseRedLevel = 0,
                BarBaseGreenLevel = 0.5f,
                BarBaseBlueLevel = 0,
                GradientStartX = 0.5f,
                GradientStartY = 0,
                GradientEndX = 0.5f,
                GradientEndY = 1
            };

            CreateRectangleAnimatedRectangle(barOne);
            CreateRectangleAnimatedRectangle(barTwo);
            CreateRectangleAnimatedRectangle(barThree);
            CreateRectangleAnimatedRectangle(barFour);
        }

        private void CreateRectangleAnimatedRectangle(BarDescriptor inputParameters)
        {
            DoubleAnimation firstStopAnim =new DoubleAnimation(0.01, 0.95,new Duration(new TimeSpan(0, 0, 0, 5)));
            DoubleAnimation secondStopAnim = new DoubleAnimation(0.02, 0.96,new Duration(new TimeSpan(0, 0, 0, 5)));
            DoubleAnimation thirdStopAnim = new DoubleAnimation(0.03, 0.97,new Duration(new TimeSpan(0, 0, 0, 5)));
            DoubleAnimation fourthStopAnim = new DoubleAnimation(0.04, 0.98,new Duration(new TimeSpan(0, 0, 0, 5)));
            DoubleAnimation fifthStopAnim = new DoubleAnimation(0.05, 0.99,new Duration(new TimeSpan(0, 0, 0, 5)));

            firstStopAnim.AutoReverse = true;
            secondStopAnim.AutoReverse = true;
            thirdStopAnim.AutoReverse = true;
            fourthStopAnim.AutoReverse = true;
            fifthStopAnim.AutoReverse = true;

            firstStopAnim.BeginTime = new TimeSpan(0);
            secondStopAnim.BeginTime = new TimeSpan(0);
            thirdStopAnim.BeginTime = new TimeSpan(0);
            fourthStopAnim.BeginTime = new TimeSpan(0);
            fifthStopAnim.BeginTime = new TimeSpan(0);

            firstStopAnim.EasingFunction = new CubicEase();
            secondStopAnim.EasingFunction = new CubicEase();
            thirdStopAnim.EasingFunction = new CubicEase();
            fourthStopAnim.EasingFunction = new CubicEase();
            fifthStopAnim.EasingFunction = new CubicEase();

            GradientStopCollection gradientStops = new GradientStopCollection
            {
                new GradientStop(Color.FromScRgb(0.5f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.0),
                new GradientStop(Color.FromScRgb(0.0f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.01),
                new GradientStop(Color.FromScRgb(0.5f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.02),
                new GradientStop(Color.FromScRgb(1.0f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.03),
                new GradientStop(Color.FromScRgb(0.5f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.04),
                new GradientStop(Color.FromScRgb(0.0f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 0.05),
                new GradientStop(Color.FromScRgb(0.5f,inputParameters.BarBaseRedLevel,inputParameters.BarBaseGreenLevel,inputParameters.BarBaseBlueLevel), 1.0),
            };

            String slotOneName = RandomName();
            String slotTwoName = RandomName();
            String slotThreeName = RandomName();
            String slotFourName = RandomName();
            String slotFiveName = RandomName();

            RegisterName(slotOneName, gradientStops[1]);
            RegisterName(slotTwoName, gradientStops[2]);
            RegisterName(slotThreeName, gradientStops[3]);
            RegisterName(slotFourName, gradientStops[4]);
            RegisterName(slotFiveName, gradientStops[5]);

            LinearGradientBrush gradientBrush = new LinearGradientBrush(gradientStops,new Point(inputParameters.GradientStartX,
                inputParameters.GradientStartY),new Point(inputParameters.GradientEndX,inputParameters.GradientEndY));

            Storyboard.SetTargetName(firstStopAnim, slotOneName);
            Storyboard.SetTargetProperty(firstStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard.SetTargetName(secondStopAnim, slotTwoName);
            Storyboard.SetTargetProperty(secondStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard.SetTargetName(thirdStopAnim, slotThreeName);
            Storyboard.SetTargetProperty(thirdStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard.SetTargetName(fourthStopAnim, slotFourName);
            Storyboard.SetTargetProperty(fourthStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard.SetTargetName(fifthStopAnim, slotFiveName);
            Storyboard.SetTargetProperty(fifthStopAnim,new PropertyPath(GradientStop.OffsetProperty));

            Storyboard gradientAnimation = new Storyboard { RepeatBehavior = RepeatBehavior.Forever };

            gradientAnimation.Children.Add(firstStopAnim);
            gradientAnimation.Children.Add(secondStopAnim);
            gradientAnimation.Children.Add(thirdStopAnim);
            gradientAnimation.Children.Add(fourthStopAnim);
            gradientAnimation.Children.Add(fifthStopAnim);

            _testRect = new Rectangle
            {
                Width = inputParameters.RectangleWidth,
                Height = inputParameters.RectangleHeight,
                Stroke = Brushes.White,
                StrokeThickness = 1,
                Fill=gradientBrush
            };

            Root.Children.Add(_testRect);
            Canvas.SetLeft(_testRect, inputParameters.RectangleX);
            Canvas.SetTop(_testRect, inputParameters.RectangleY);

            gradientAnimation.Begin(this);
        }

        private string RandomName()
        {
            const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
            const int nameLen = 8;
            var random = new Random();
            string temp = Guid.NewGuid().ToString().
               Replace("-", String.Empty);
            temp = Regex.Replace(temp, @"[\d-]",
               string.Empty).ToUpper();
            return new string(Enumerable.Repeat(chars, nameLen).Select
               (s => s[random.Next(s.Length)]).ToArray()) + temp;
        }
    }
}


WPF 有趣的动画效果