动画(animation)是 Silverlight 一项关键特性。它提供了炫目的视觉效果,这是基于服务器编程的框架无法仿效的(例如 ASP.NET)。在 Sileverlight 中动画可以实现很多效果(例如,鼠标经过时图标变大、logo 旋转、文本滚入视图等),也可以用来实现更宏大的商业设计和基于浏览器的游戏。
动画是 Silverlight 模型的核心部分。这意味着你不需要以计时器和事件处理代码来实现它们,而是通过少数几个类来声明并配置它们。
动画基础知识
Silverlight 动画是一个精简版的 WPF 动画系统。为了便于理解,需要了解以下关键规则:
- Silverlight 执行以时间为基础的动画。因此,需要设定初始状态、最终状态、持续时间,而 Silverlight 计算出帧速。
- Silverlight 使用基于属性的动画模型。这意味着一个动画只能做一件事:修改时间间隔的属性值。在很多方面,这是个很大的限制,但仅仅修改属性,也能创建出多得超乎想像的效果了。
- 要赋予动画一个属性,就需要使用一个支持这种数据类型的动画类。例如,修改数据类型为 double 的属性,必须使用 DoubleAnimation 类。
Silverlight 中的动画类比较少,只有有限的数据类型可以使用。目前,能用来修改动画属性的数据类型有 double、object、Color、Point。
定义动画
创建动画是一个多步的过程。需要创建 3 个要素:执行动画的动画对象、管理动画的演示图板、启动演示图板的事件触发器。
Silverlight 有两种动画类,采用不同的变更属性值策略:
- 线性插值:使用线性策略,属性值在动画的持续时间内逐渐变化,这种策略的例子包括 DoubleAnimation、ColorAnimation、PointAnimation。
- 关键帧动画:使用关键帧策略,属性值可以从一个值跳跃到另一个值,或者结合跳跃和线性插值策略。这种策略的例子包括 DoubleAnimationUsingKeyFrames、ColorAnimationUsingKeyFrames、PointAnimationUsingKeyFrames。
本文只介绍最常用的动画类:DoubleAnimation 类。它采用线性插值,在最小值和最大值之间选择双精度值。和所有动画类一样,被定义在 System.Windows.Media.Animation 命名空间中。
虽然动画并不是元素,但仍然可用 XAML 标记定义。下面的标记创建一个 DoubleAnimation :
<DoubleAnimation From="160" to="300" Duration="0:0:5"></DoubleAnimation>
这个动画持续5秒(Duration 属性格式:时:分:秒:毫秒),目标值在 160 – 300 之间变化。如果这个 DoubleAnimation 能以 Silverlight 默认的最高帧速运行,动画值每秒调整 60 次。
StoryBoard 类
演示图板用来管理动画的时间轴,可以用来组织多个动画,也可以用它来控制动画的播放、暂停、停止和改变它的位置。StoryBoard 类提供的最基本功能却是用 TargetProperty 和 TargetName 属性来指定特定的属性和特定的元素。换言之,演示图板是动画和要设置的动画属性之间的桥梁。
下面的标记定义一个演示图板,为名为 cmdGrow 的按钮的 Width 属性赋予 DoubleAnimation 类:
<Storyboard x:Name="storyboard" Storyboard.TargetName="cmdGrow"
Storyboard.TargetProperty="Width">
<DoubleAnimation From="160" To="300" Duration="0:0:5" ></DoubleAnimation>
</Storyboard>
Storyboard.TargetName 定义了要应用动画的目标元素,Storyboard.TargetProperty 定义了想在目标元素中改变的属性。如果要设置一个附加属性,例如 Canvas.Top,那么需要将整个属性名用括号括起来:
<Storyboard x:Name="storyboard" Storyboard.TargetName="cmdGrow"
Storyboard.TargetProperty="(Canvas.Top)">
<DoubleAnimation From="160" To="300" Duration="0:0:5" ></DoubleAnimation>
</Storyboard>
TargetName 和 TargetProperty 都属于附加属性,这意味直接在动画上应用这些属性也是可以的:
<Storyboard x:Name="storyboard">
<DoubleAnimation From="160" To="300" Duration="0:0:5"
Storyboard.TargetName="cmdGrow" Storyboard.TargetProperty="Width">
</DoubleAnimation>
</Storyboard>
这种语法是较为常见的。因为它可以让你把多个动画放在一个演示图板里,并且允许每个动画拥有不同的元素和属性。尽管不能为多个动画在同一时间指定相同的属性,但可以(而且往往会)在同一时间为相同元素指定不同的属性。
所有的 Silverlight 元素都有一个 Resources 属性,用以提供一个集合来存储各种对象。Resources 集合的主要目的是让你能够在 XAML 中定义那些不属于元素,因而也就不能被置于可见内容区域的对象。Resources 可以在代码中进行检索或者在标记中别的地方使用。对于那些按钮增长型动画来说,Resources 集合是一个方便的存储位置:
<UserControl ...>
<UserControl.Resources>
<Storyboard x:Name="storyboard">
<DoubleAnimation Storyboard.TargetName="cmdGrow" Storyboard.TargetProperty="Width"
From="160" To="300" Duration="0:0:5" ></DoubleAnimation>
</Storyboard>
</UserControl.Resources>
<Grid>
<Button x:Name="cmdGrow" Width="160" Height="30"
Content="This button grows" Click="cmdGrow_Click"></Button>
</Grid>
</UserControl>
如果你想在启动动画之前通过程序调整它的属性,你也可以给 DoubleAnimation 添加一个名称,这样你可以在代码中访问到它。
现在,只需要在事件处理程序中调用 StoryBoard 对象的方法即可。这些方法包括 Begin()、Stop()、Pause()、Resume()、Seek()。
private void cmdGrow_Click(object sender, RoutedEventArgs e)
{
storyboard.Begin();
}
配置动画属性
想要将动画这个功能发挥到极致,就必须认真研究 Animation 基类。这个类定义了所有动画类的属性:
名 称 |
描 述 |
From | 设置动画的初始值。很多时候不必设置,Silverlight 将使用元素的当前值。有时,你希望动画从当前值开始,而不是跳转到一个预设的 From 值。 |
To | 设置动画的结束值。 |
By | 使用 By 而不是 To 来创建一个持续增长的动画。给 By 设置一个值,这个值会和初始值累加在一起。之前的示例中,设置 By=10,去除 From 和 To 的设置,每次单击会使矩形按钮不断的增长 |
Duration | 运行的持续时间 |
AutoReverse | 如果设置为 true,动画结束之后会倒着运行一次回到初始值,持续时间会增加一倍 |
RepeatBehavior | 你可以设置重复运行动画的次数,或者使用 Forever 无休止的重复动画 |
BeginTime | 在动画开始之前设置一个延迟时间。如果你要将不同的动画进行同步,即同一时间启动所有动画,但是动画效果却要按序列运行,这非常有用 |
SpeedRatio | 增加或降低动画运行的速度。默认是 1 。设置成 5 将 5 倍加速完成。 |
FillBehavior | 决定动画结束时属性值的状态。通常,会保留属性值为动画结束时的状态(FillBehavior.HoldEnd),但也可选择让它回到初始值(FillBehavior.Stop) |
交互式动画实例
有时,你需要在程序中用代码创建动画的每一个细节,这种情况是相当普遍的。通过编程、配置以及启动动画并不困难。首先,创建动画以及演示图板对象并将动画添加到演示图板中。当这些动画结束的时候,还需要响应 Storyboard.Completed 事件来清理动画。
下面的例子演示了更贴近现实的动画的使用。一开始内容区域充满了形状不规则的矩形。单击矩形它会下降到画布的底部并同时改变颜色。可以连续快速的单击多个矩形以便同时运行多个动画。网页将使用多个演示图板,每个目前正在下降的矩形使用一个演示图板。当有需要时,这些演示图板就会被创建。
本例中的标记定义了一个包含有 Border 和一个 Canvas 的简单页面。它并不包含演示图板,因为这些细节需要在矩形被单击时动态创建:
<UserControl x:Class="SilverlightApplication1.AnimationPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Width="500" Height="500" Loaded="Page_Loaded">
<Border BorderBrush="SteelBlue" BorderThickness="1">
<Canvas x:Name="canvas" Background="AliceBlue"></Canvas>
</Border>
</UserControl>
这个画布并不包含任何其他元素,因为这里将动态生成矩形。当画布被加载时,它在随机的位置创建 20 个大小随机的矩形。这些矩形采用相同的 MouseLeftButtonDown 事件处理程序:
private void Page_Loaded(object sender, RoutedEventArgs e)
{
// Generate some rectangles.
Random random = new Random();
for (int i = 0; i < 20; i++)
{
Rectangle rect = new Rectangle();
rect.Fill = new SolidColorBrush(Colors.Red);
// Size and place it randomly.
rect.Width = random.Next(10, 40);
rect.Height = random.Next(10, 40);
Canvas.SetTop(rect, random.Next((int)(this.Height - rect.Height)));
Canvas.SetLeft(rect, random.Next((int)(this.Width - rect.Width)));
// Handle clicks.
rect.MouseLeftButtonDown += new MouseButtonEventHandler(rect_MouseLeftButtonDown);
// Add it to the Canvas
canvas.Children.Add(rect);
}
}
当一个矩形被单击时,需要创建新的演示图板及相应的动画。在本例中,你需要两个动画分别用来修改矩形的属性。DoubleAnimation 用来改变 Canvas.Top 属性使得矩形可以下降;ColorAnimation 用来混合矩形的颜色。
这个演示图板会被加入到集合中,这样你可以轻松跟踪当前运行的演示图板以及相应的动画元素。通过一个事件处理程序,代码可以在上一个动画结束时接收到通知,然后启动这个动画。
void rect_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Rectangle rect = sender as Rectangle;
// Create the storyboard for the rectangle
Storyboard storyboard = new Storyboard();
// Create the animation for moving the rectangle.
DoubleAnimation fallingAnimation = new DoubleAnimation();
Storyboard.SetTarget(fallingAnimation, rect);
Storyboard.SetTargetProperty(fallingAnimation, new PropertyPath("(Canvas.Top)"));
fallingAnimation.To = this.Height - rect.Height;
fallingAnimation.Duration = TimeSpan.FromSeconds(2);
storyboard.Children.Add(fallingAnimation);
// Create the animation for changing the rectangle's color.
ColorAnimation colorAnimation = new ColorAnimation();
Storyboard.SetTarget(colorAnimation, rect.Fill);
Storyboard.SetTargetProperty(colorAnimation, new PropertyPath("Color"));
colorAnimation.To = Colors.Blue;
colorAnimation.Duration = fallingAnimation.Duration;
storyboard.Children.Add(colorAnimation);
// Track the rectangle.
animatedShapes.Add(storyboard, rect);
// React when the storyboard is finished.
storyboard.Completed += new EventHandler(storyboard_Completed);
// Start the storyboard.
storyboard.Begin();
}
当演示图板结束时,就到了清理的时间。下面是用来完成这一任务的最简单的代码:
void storyboard_Completed(object sender, EventArgs e)
{
Storyboard storyboard = sender as Storyboard;
storyboard.Stop();animatedShapes.Remove(storyboard);
}
然而有一个问题,动画实际上并不真正改变这些属性的值,只不过是临时覆盖它,只是你不会注意到这个事实,因为完成的动画并没有真正停止。相反,当一个动画结束时,它的属性会继续保持在动画结束时的状态。这意味着,在演示图板结束之后,完成了动画的矩形仍会停留在页面的底部,并且仍然是蓝色。
问题是,当动态创建演示图板时,可能存在相当多的演示图板在同一个时间执行的情况。为了确保良好的性能,在动画结束时明确终止演示图板就变得非常重要。就像上述代码所作的那样。
然而,新问题又来了。终止动画会使得动画的属性回到初始值。在这个例子中,意味着每一次终止一个下降的矩形,它将会跳转到原来的位置,恢复原来的红色。解决办法是检索矩形的 Canvas.Top 属性的当前值(这就是声明 Dictionary 结合的原因,可以获取到矩形元素的引用),然后终止动画,并将这个值重新赋予这个矩形:
void storyboard_Completed(object sender, EventArgs e)
{
// Stop the animation but keep the new position
Storyboard storyboard = sender as Storyboard;
Rectangle rect = animatedShapes[storyboard];
rect.Fill = new SolidColorBrush(Colors.Blue);
double newTop = Canvas.GetTop(rect);
storyboard.Stop();
Canvas.SetTop(rect, newTop);
// Remove it from the tracking collection.
animatedShapes.Remove(storyboard);
}
变换
Silverlight 动画是通过修改属性值工作的。元素有多个属性值可以修改,这非常有用。设置 Canvas.Top、Cnavas.Left 来将一个元素四处移动。改变 Opactity 可以使得元素淡入淡出。
更令人兴奋的变更,例如旋转。秘诀就是变换。变换是一个对象,通过切换坐标系来更改一个形状或者其他元素的绘制方向。在 Silverlight 用户界面中,使用变换来伸展、旋转、扭曲来操控形状、图像和文字。
Silverlight 支持的变换(类)
名称 |
描述 |
重要属性 |
TranslateTransform | 平移坐标系统。适用于在不同位置画出相同的形状 | X、Y |
RotateTransform | 旋转坐标系统。围绕你选择的中心点进行旋转 | Angle、CenterX、CenterY |
ScaleTransform | 拉伸坐标系统。拉伸或压缩形状 | ScaleX(Y)、CenterX(Y) |
SkewTransform | 倾斜坐标系统。以一定度数扭曲,例如,将正方形变成一个平行四边形 | AngleX、AngleY
CenterX、CenterY |
MatrixTransform | 改变坐标系统。用你提供的矩阵进行矩阵相乘 | Matrix |
TransformGroup | 组合多种变换方式,使得所有方式可以一起使用。你设置的变换的排列顺序非常重要。 |
从技术上讲,所有的变换都采用矩阵数学的方式来改变形状的坐标。然而,使用预先定义好的变换(TranslateTransform、RotateTransform、ScaleTransform、SkewTransform),将会比用 MatrixTransform 并试图为想要实现的操作找到正确的矩阵简单很多。
当使用 TransformGroup 执行一系列变换时,Silverlight 会将你的变换融合成一个单一的 MatrixTransform 确保得到最佳性能。
1. 使用变换
要变换一个元素时,需要设置想要使用的变换对象的 RenderTransform 属性。根据要使用的变换类型,你需要为其配置不同的属性(如上表)。例如,将一个按钮顺时针旋转 25 度:
<Button Content="A Button">
<Button.RenderTransform>
<RotateTransform Angle="25"></RotateTransform>
</Button.RenderTransform>
</Button>
如果用这种方式旋转一个元素,就是围绕元素的起点(左上角)开始旋转。如果要围绕不同的点来旋转一个形状,可以使用方便的 RenderTransformOrigin 属性。这个属性使用相对坐标系统来设置中心点,中心点的值介于 0 和 1 之间。换言之,点(0,0)被指定为左上角,点(1,1)则是右下角。(如果这个元素不是正方形,坐标系统就会相应地拉伸)
在 RenderTransformOrigin 的帮助下,你可以使用如下标记围绕其中心点旋转任何元素:
<Button Content="One" RenderTransformOrigin="0.5,0.5">
<Button.RenderTransform>
<RotateTransform Angle="25"></RotateTransform>
</Button.RenderTransform>
</Button>
这段标记能达到我们想要的效果,因为无论这个形状大小是多少,点(0.5,0.5)都表示它的中心点。你也可也用 RenderTransformOrigin 属性指定形状边界之外的一个点,可以使用大于 1 或者小于 0 的值,例如,以一个非常遥远的点为圆心(5,5)。
2. 动画变换
要想在动画中使用变换,第一步是定义变换,动画可以改变现有的变换但是不能创建新的变换。例如下面示例:
<Button x:Name="cmd" Content="A Button" RenderTransformOrigin="0.5,0.5">
<Button.RenderTransform>
<RotateTransform x:Name="buttonTransform"></RotateTransform>
</Button.RenderTransform>
</Button>
现在这里有一个动画,当鼠标经过一个按钮时,按钮将旋转。这个动画作用于 Button.RotateTransform 对象,并且使用了 Angle 属性:
<Storyboard x:Name="rotateStoryboard">
<DoubleAnimation Storyboard.TargetName="buttonTransform"
Storyboard.TargetProperty="Angle" To="360" Duration="0:0:0.8"
RepeatBehavior="Forever"></DoubleAnimation>
</Storyboard>
这个按钮每 0.8 秒旋转一次,并且会持续不断进行旋转。旋转中的按钮仍然完全可用。例如,你可以单击它并处理 Click 事件。
private void cmd_MouseEnter(object sender, MouseEventArgs e)
{
rotateStoryboard.Begin();
}
要停止旋转,可以响应 MouseLeave 事件。这样的话,虽然可以停止执行旋转的演示图板,但这会导致按钮一下子跳转回初始方向。