第7章
动画
Silveright支持动画(Animation)的制作,通过属性的调整,在指定的时间区间内,完成属性的改变,以达到动画的效果。Silverlight有两种形式的动画——From/To/By与Key frame,开发人员可以根据所要完成的动画效果,选择合适的动画技术。
动画可以让可视化接口呈现非常炫丽的效果。在Silverlight的环境下,通过XAML语法,不需要太费力,就可以制作出具相当水平的动画。但是在此提醒大家,动画虽然可以让网页更引人注目,但是过量与不当的动画,却很容易让效果相反,因此,避免滥用动画是在设计Silverlight动画内容时必须谨记在心的。
7.1 Silverlight动画
传统的动画通过快速播放一系列的图片或是场景建立动态影像的效果,而Silverlight的作法不太一样,它是经由相关对象属性的调整来达到动画的效果。例如,更改矩形对象的宽度与高度等属性值以动态改变其大小,通过相关的Animation类完成动画效果的制作,本节将先介绍相关的概念。
7.1.1 动画概观
Silverlight提供了一些可用的类支持动画的制作,特别是这些类根据所要变动的属性类型设计,如DoubleAnimation被套用于Double类型的属性值,动态改变矩形宽度便是运用DoubleAnimation非常典型的场合,其他还有用来调整表现色彩的ColorAnimation以及对象位置坐标的PointAnimation等。
Animation类只是设定动画的行为,例如对象的淡入淡出、移动或是变形等。要让对象能够动起来,呈现真正的动画效果,必须进一步指定动画的关联事件,也就是触发动画的事件程序,这部分通过EventTrigger的RoutedEvent属性进行设定。最后,还要将Animation对象配置于Storyboard对象标记中,这个标记用来指定动画的目标属性。
动画是一种事件驱动的动态视觉效果,Animation类用来定义动画的行为,并且关联至某个特定的事件,当关联的事件被触发时,定义好的动画便会被播放。范例7-1,我们看到了相关的应用,现在深入了解其中的细节。
动画本身由事件进行驱动,因此,我们在事件区块中定义Animation类,如以下的XAML。
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
…
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
这一段XAML是前面我们所讨论的矩形宽度动态缩放动画的事件内容,其中的EventTrigger是一种事件触发器,用来描述当一个被监控的事件发生时,所要触发的行为内容,其中的RoutedEvent用来设定所要监控的事件。
EventTrigger与一般开发人员所熟悉的事件处理器基本上一样,只是它让我们可以直接通过XAML设计事件的相关处理程序,而不需要编写程序代码。
通过RoutedEvent指定了事件的种类,接下来,便是设计事件所要引发的动作,这些动作用来响应被触发的事件,在这里指的便是所要呈现的动画特效。
动画由Animation类定义,而Storyboard描述Animation类建立的动画效果所要套用的目标对象,因此,必须将Animation类声明成为Storyboard的子标记项目,只有这样,其中所定义的动画特效才能套用至Storyboard所指定的目标对象。因此,我们在范例7-1中看到以下的Storyboard标记结构。
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="AnimatedRectangle"
Storyboard.TargetProperty="Width"
… />
</Storyboard>
TargetName与TargetProperty为Storyboard的成员,表示其中的子标记DoubleAnimation所要关联的目标对象以及目标属性。
当然,事情还没有结束,所要指定的事件确定了,响应事件的内容程序也写好了,最后,如何将它们串起来?答案是BeginStoryboard,这个对象用来触发一个Storyboard的操作。EventTrigger在某个事件被触发时,必须知道接下来要响应此事件的操作,我们必须指定合适的操作给它,而BeginStoryboard是其中之一,通过这个对象,我们便可以将事件、目标对象,以及相关的动画效果完整的串连起来,在网页上呈现设计好的效果,因此,最后我们会看到以下的程序代码。
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
…
</Storyboard>
</BeginStoryboard>
</EventTrigger>
想要建立动画,必须通过EventTrigger、Storyboard与Animation等几个标记进行制作,提供如下3个主要的信息。
● 指定动画的触发时机。
● 选择动画的类型。
● 所要套用的动画类。
当完成上述的XAML程序代码,就可以建立一个自定义的动画。
7.1.2 一个简单的动画范例
7.1.1节介绍了一些在Silverlight制作动画所需的概念,有了初步的认识之后,现在我们以一个最简单的动画范例,来制作上述说明的内容。
【范例7-1】 AControl
这个范例示范动态缩放矩形宽度的动画,以下列举动画的XAML内容。
<?xml version="1.0" encoding="utf-8" ?>
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480"
Background="White"
>
<Rectangle
Name="AnimatedRectangle"
Width="100"
Height="100"
Fill="Blue">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="AnimatedRectangle"
Storyboard.TargetProperty="Width"
From="0.0" To="200" Duration="0:0:5"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Canvas>
这个范例示范动态缩放矩形宽度的动画效果,首先,我们要在矩形对象加载时触发动画的执行,因此,指定EventTrigger的RoutedEvent属性为Loaded,然后在Storyboard对象中指定动画的目标是名称为AnimatedRectangle的Rectangle对象,目标属性是Width,即矩形的宽度。最后,设定DoubleAnimation对象的From以及To属性,表示矩形对象的宽度,动态地将属性值从0转变至200。
图7-1是动画执行的结果。首先,画面开始加载时,会逐渐显示一个矩形,然后这个矩形的宽度会逐渐变长,最后再恢复原状。
图7-1 动画过程
除了DoubleAnimation,同样,我们也可以通过ColorAnimation类制作颜色转换的动画,另一个PointAnimation类,则支持对象移动时的坐标移动设定等动画效果制作。
7.1.3 Silverlight动画类与属性
无论是哪一个类型的动画,它们均针对了特定的对象属性类型扩展出相关的类,如表7-1所示。
表7-1 动画扩展类
属 性 类
From/To/By
Color
ColorAnimation
Double
DoubleAnimation
Point
PointAnimation
这些类对应至特定的对象属性类型,我们必须针对所要驱动的对象属性类型,选择合适的类。后文针对特定类的应用,会有进一步的说明。
这些Animation类都是名称为TimeLine类的子类,它们具有共通的特性,如范例7-1所提及的Duration、AutoReverse以及RepeatBehavior等,这些设定均会影响到动画的效果。
Animation类的属性设定对动画效果的影响非常大,这些属性项目指示动画应该如何呈现它的效果。在一开始的范例中,我们便示范了相关属性的设定,现在,一一来说明这些属性的意义。
为了方便说明,将其列举如下。
<DoubleAnimation
From="0.0" To="200" Duration="0:0:5"
AutoReverse ="True" RepeatBehavior="Forever" />
首先是From以及To,分别设定动画关联对象的属性,动画效果开始与结束的属性值,这段XAML设定可以被套用至如矩形宽度与长度等Double类型的属性。针对其他不同的Animation类,这两个值必须套用对等的类型值,例如,假设我们所要套用的类是ColorAnimation,则必须如下式指定From以及To这两个属性值。
From="Red" To="Blue"
我们必须指定一个代表颜色的属性值字符串给它,因为ColorAnimation是针对颜色转换动画特效而设计的Animation类。
From与To,定义了动画开始与结束的对象属性值,接下来的3个属性——Duration、AutoReverse及RepeatBehavior,则用来定义动画的过程如何进行。
Duration代表一个时间区间,用来定义一段timeline的时间长度,也就是播放一个动画所需的时间长度,其格式如下所示。
hours : minutes : seconds
假设在动画的设计过程中,没有指定这个值,则其值为0:0:1,也就是1秒,甚至可以指定非整数的秒数,例如,1:30:10.5表示1小时30分钟10.5秒。
在Duration指定的时间长度结束之后,动画便会自动结束,当然,也可以令其重复执行。RepeatBehavior这个属性让我们设定重复的行为,或是设定所要重复的次数,甚至指定一个Forever给它,要求其永久重复执行。
AutoReverse属性用来指定是否在到达Duration的时间长度时,逆向播放动画,默认为false,如果设定为true,动画会在播放完毕之后,逆向重新再播放一次。
到目前为止,我们已经建立动画设计必须了解的一些重要知识,以下来看一个范例,其中通过ColorAnimation制作出颜色转换动画。
【范例7-2】 ColorAnimation
这个范例针对一个矩形进行颜色的转换,一开始加载时,会从Blue转换至Red,以下是XAML程序代码。
<?xml version="1.0" encoding="utf-8" ?>
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800" Height="800">
<Rectangle
Fill ="Black" Stroke="Blue"
Width="300" Height="200"
Canvas.Left="16" Canvas.Top="48"
x:Name="rct" >
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="rct"
Storyboard.TargetProperty="(Fill).(Color)"
From="Blue" To="Red"
Duration="0:0:5"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Canvas>
其中的ColorAnimation在Rectangle对象加载的时候,调整其Fill属性值,在5秒的时间区间内,从Blue转换成为Red。在Storyboard.TargetProperty设定其属性值时,请特别注意,必须指定如下的值给它。
(Fill).(Color)
这个属性值的表示法与上述DoubleAnimation直接指定Width有所不同。
当执行这个范例时会看到以下的结果,其中矩形的颜色从蓝色逐步转换至红色,如图7-2所示。
图7-2 颜色变化动画
除了直接指定如矩形对象的Fill属性,我们也可以从Brush的属性作设定。接下来看另一个范例,这个范例套用了Brush对象,进行颜色的转换动画。
【范例7-3】 ColorAnimationBrush
基本上,这个范例与上述的范例内容相同,但是以Brush对象取代了原来的Fill属性,XAML内容如下。
<?xml version="1.0" encoding="utf-8" ?>
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800" Height="800">
<Rectangle
Stroke="Blue"
Width="100" Height="300"
Canvas.Left="16" Canvas.Top="48"
x:Name="rct" >
<Rectangle.Fill>
<SolidColorBrush x:Name="rctbrush" Color="black"/>
</Rectangle.Fill>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="rctbrush"
Storyboard.TargetProperty="Color"
From="Orange" To="Blue"
Duration="0:0:5"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Canvas>
由于设定了SolidColorBrush,因此,ColorAnimation这个对象的Storyboard.TargetName属性必须指定为所使用的Brush对象名称,而Storyboard.TargetProperty这个属性则只要指定Color即可。最后,会在执行时,看到类似上述的颜色转换动画,这个范例从Orange转换至Blue。
7.2 控制动画
在本节,我们要进一步来看看如何控制动画的播放。在此之前的范例,我们仅简单地在事件触发时,开始播放动画,最多只是设定其重复或是逆向播放。事实上,还可以针对动画的播放,进行更多的控制,如何为应用程序加入如停止、暂停、暂停恢复等相关功能。
动画播放的控制是经由Storyboard所提供的方法作设定,相关的方法有以下4种。
● Begin,初始化Storyboard。
● Pause,暂停Storyboard。
● Resume,恢复暂停的Storyboard。
● Stop,停止Storyboard。
完成与动画相关的Storyboard配置之后,接下来,就可以通过JavaScript引用相关的方法,控制动画的播放。
JavaScript建立事件处理程序,响应使用者操作Silverlight对象所触发的各种事件,如按一下鼠标左键、或是鼠标移动经过特定区域所触发的相关事件,我们在这些事件中,引用关联的方法控制动画的播放。
以下调整本章范例7-1所示范的AControl,并进行相关的说明。这个范例命名为AControlMDown,一开始播放一段动画,将画面上矩形对象宽度放大两倍,然后回复至原来的大小,相关的XAML内容如下。
【范例7-4】 AControl MDown
<?xml version="1.0" encoding="utf-8" ?>
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480"
Background="White"
>
<Rectangle
x:Name="AnimatedRectangle"
Width="100"
Height="100"
Fill="Blue"
MouseLeftButtonDown="AnimatedRectangle_Click">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard x:Name="RStoryboard">
<DoubleAnimation
Storyboard.TargetName="AnimatedRectangle"
Storyboard.TargetProperty="Width"
From="100" To="200" Duration="0:0:2"
AutoReverse="True" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Canvas>
对这个XAML文件的内容,相信大家已经具有相当的概念,在此便不再说明。请特别注意其中矩形对象的MouseLeftButtonDown事件处理程序名称为AnimatedRectangle_Click,而Storyboard对象名称设定为RStoryboard。下面我们在以下的JavaScript中,针对对象进行相关方法的引用。
function AnimatedRectangle_Click(sender, mouseEventArgs)
{
sender.findName("RStoryboard").begin();
}
其中的findName用来取得Storyboard对象,并且引用其begin方法,重新将动画再播放一次。
这个范例的执行结果与前面示范的相同,只是当按一下上面的矩形时,动画会重新开始播放,请自行尝试看看。
我们也可以加入更多的对象,编写相关的事件处理程序,以下这个范例AControlF将进行完整的说明。
【范例7-5】 AControlF
这个范例在上述的矩形画面中,加入了3个控制播放的TextBlock,我们来看它的XAML内容。为了节省篇幅,其中省略了上述重复的内容。
<?xml version="1.0" encoding="utf-8" ?>
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480"
Background="White"
>
<Rectangle …>
…
</Rectangle>
<TextBlock Width="32" Height="24"
Canvas.Left="16" Canvas.Top="120"
Text="Play" TextWrapping="Wrap" x:Name="btnBegin"
Foreground="#FF0102FF"
MouseLeftButtonDown="Begin_Click" Cursor="Hand" />
<TextBlock Width="48" Height="24"
Canvas.Left="64" Canvas.Top="120"
Text="Pause" TextWrapping="Wrap"
Foreground="#FF0102FF"
x:Name="btnPause"
MouseLeftButtonDown="Pause_Click" Cursor="Hand"/>
<TextBlock Width="32" Height="24"
Canvas.Left="128"
Canvas.Top="120" Text="Stop"
TextWrapping="Wrap" Foreground="#FF0102FF"
x:Name="btnStop"
MouseLeftButtonDown="Stop_Click" Cursor="Hand"/>
</Canvas>
其中在画面上建立了3个TextBlock,每一个TextBlock均设定了其关联的MouseLeftButtonDown事件处理程序。接下来,我们在JavaScript文件中建立相关的事件处理程序,如下所示。
function Begin_Click(sender, mouseEventArgs)
{
sender.findName("RStoryboard").begin();
}
function Stop_Click(sender, mouseEventArgs)
{
sender.findName("RStoryboard").Stop();
}
function Pause_Click(sender, mouseEventArgs)
{
sender.findName("RStoryboard").Pause();
}
这些事件处理程序分别取得Storyboard的执行个体,然后引用相关的方法控制Silverlight的播放。
图7-3是这个范例的执行画面,其中显示了3个TextBlock,分别可以让我们开始、暂停以及停止动画的播放。
图7-3 控制动画播放
7.3 Key-Frame动画
至此,我们大致上讨论了制作动画所需的基本知识,同时运用了一些范例进行相关的说明,紧接着来看看另外一种类型的动画—— Key-Frame动画。不同于前面提及的From/To/By动画,这种类型的动画,以Key frames 对象取代From/To/By的设定,通过特定的内插法描述动画特效,能够制作出更为丰富且复杂的动画效果。
7.3.1 内插法与Key-Frame动画对象
Silverlight提供两种类型的动画——From/To/By动画与Key-Frame动画,两者最主要的差异在于目标对象的相关属性设定方式,Key-Frame动画在动画的设计上,提供了更大的弹性。7.2节范例所使用的是From/To/By类型的动画,这类动画我们只能通过Storyboard,经过特定类型对象的From以及To属性,指定对象动画属性值,这种形式的设计对于复杂动画的发展限制极大。此时,Key-Frame动画是另外一种可用的选择。
Key-Frame动画有其专属的Animation类,这些类与From/To/By Animation意义相同,只是名称格式多了UsingKeyFrames,如表7-2所示。
表7-2 Key-Frame动画类
属 性 类
Key-Frame 动 画
Color
ColorAnimationUsingKeyFrames
Double
DoubleAnimationUsingKeyFrames
Point
PointAnimationUsingKeyFrames
同样,它们被套用于对等类型的对象属性。
Key-Frame动画进一步通过内插法定义动画所要呈现的效果,无论上述列表中的哪一个类,均同时支持3种类型的内插法。内插法被用来描述指定值之间的转换作业。Key-Frame动画支持3种类型的内插法——Discrete、Linear与Spline,从这些名称,我们大概可以理解它们所支持的动画特效类型,其命名格式如下。
<InterpolationMethod><Type>KeyFrame
以此惯例命名的3个类是DiscreteDoubleKeyFrame、LinearDoubleKeyFrame与SplineDoubleKeyFrame,我们只要根据所要套用的内插法引用相关的类即可。
在Key-Frame动画对象中,同样必须设定Duration属性,以指定动画所要播放的时间片段长度。至于动画效果的描述,则必须选择所要引用的内插法,设定value以及KeyTime属性,取代From/To/By的属性设定。我们来看看这两者的差异,如图7-4所示。
图7-4 From/To/By动画所设定的Duration
From/To/By以动画对象所设定的Duration,其定义的时间长度为单位描述动画,并且由From与To这两个属性,定义这个单位时间的开始与结束值。
图7-5是Key-Frame动画架构的示意图。
图7-5 Key-Frame动画所设定的Duration
Key-Frame动画将Duration进一步切割成为更小的时间单位,每一个切割的单位则由内插对象KeyFrame描述所要定义的动画效果,以呈现更复杂的动画。
内插对象的主要属性是KeyTime与value,KeyTime表示所要切割的时间单位长度,value则是在这个时间单位中呈现出动画效果所要达到的值。
为了更具体说明Key-Frame动画的用法,我们来看它的语法结构。
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames … >
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="10" KeyTime="0:0:10" />
…
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
这段XAML以DoubleAnimationUsingKeyFrames为例,同样,也必须在Storyboard中嵌入DoubleAnimationUsingKeyFrames标记,然后再指定一个以上的LinearDoubleKeyFrame,以定义动画的时间单位。每一个时间单位都必须对应至一个LinearDoubleKeyFrame标记,其中的属性则进一步设定每一个时间单位的时间点以及属性值。
具备了足够的相关概念之后,我们来看看这3个内插法的细节以及相关的应用,同时借助相关的制作范例,查看它们的效果。
7.3.2 LinearDoubleKeyFrame
LinearDoubleKeyFrame是一种线性比例内插法,在Animation对象所设定的Duration期间,以等比例进行转换。假如我们设定了一个长度等于5秒钟的转换时间,然后指定最后的转换值等于10,如下所示。
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="10" KeyTime="0:0:5" />
第一个内插法与第二个内插法所定义的时间区间里,每一个时间点的输出值变化如表7-3所示。
表7-3 每一个时间点的输出值变化
KeyTime时间变化
0
1
2
3
4
5
输出值
0
2
4
6
8
10
上述范例随着时间的变化,构成动画效果的输出值以等比例进行输出,因此,得到一个线性的结果。
以Silverlight所支持的3种Key-Frame类型动画而言,ColorAnimationUsingKeyFrames在第一个LinearDoubleKeyFram的Value属性所指定的颜色值等比例的转换至第二个LinearDoubleKeyFram的Value属性值。
同样的,DoubleAnimationUsingKeyFrames与PointAnimationUsingKeyFrames会显示相同的线性效果,前者为包含对象大小等特性的改变,后者则是对象位置的动态改变。
我们通过一个范例KFAnimation,进行具体说明。
【范例7-6】 KFAnimation
这个范例建立了一个矩形方块,当网页加载时,这个矩形方块会在画面上根据Linear- DoubleKeyFrame指定的坐标点以及时间区段,进行位移的动态效果,相关的XAML如下。
<?xml version="1.0" encoding="utf-8" ?>
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480"
Background="White"
>
<Rectangle Fill="Blue"
Width="50" Height="50">
<Rectangle.RenderTransform>
<TranslateTransform
x:Name="AnimatedTransform"
X="0" Y="0" />
</Rectangle.RenderTransform>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="AnimatedTransform"
Storyboard.TargetProperty="X"
Duration="0:0:10">
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="100" KeyTime="0:0:1" />
<LinearDoubleKeyFrame Value="200" KeyTime="0:0:2" />
<LinearDoubleKeyFrame Value="400" KeyTime="0:0:3" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Canvas>
在这个XAML中,DoubleAnimationUsingKeyFrames将Duration的长度等设为10秒钟,同时进一步设定了4个LinearDoubleKeyFrame,及切割出4个时间单位,并且设定这4个时间单位最后所要到达的Value值。
这里的TargetProperty设为X,而且所引用的是线性内插法,因此,矩形在各时间区段,以等比例的速率左右水平移动,依据4个TargetProperty,其变化如表7-4所示。
表7-4 动画在不同区段的输出状态
LinearDoubleKeyFrame
动画输出状态
Value="0" ,KeyTime="0:0:0"
动画开始,矩形位置距离左边为0
Value="100" ,KeyTime="0:0:1"
经过一秒,矩形往右移动至距离左边100的位置
Value="200" ,KeyTime="0:0:2"
再经过一秒,矩形从距离左边100的位置,往左移动至距离左边200的位置
Value="400" ,KeyTime="0:0:3"
再经过一秒,矩形从距离左边200的位置,往右移动至距离左边400的位置
每一个LinearDoubleKeyFrame定义了一个切割的时间单位,动画最后的总时间长度为Duration所定义,在此为10秒。每一个被切割的时间单位里,X坐标值以线性等速率作改变,因此只有进入到下一个时间单位时,移动的速率才会依其定率作不同的改变,当然,改变后的速率依然是等比例输出,如图7-6所示。
图7-6 动画等比例输出
我们可以同时设定一个以上的DoubleAnimationUsingKeyFrames,例如,在其中再插入一组完全相同的LinearDoubleKeyFrame,但是将其TargetProperty设为Y,由于动画效果的输出是根据KeyTime所设定的时间点,因此,在这种情形下,可以同时改变X以及Y轴坐标,让矩形朝对角线作移动。
现在我们进一步扩充上述的XAML,为了方便说明,另外建立了一个范例,将其命名为KFAnimationXY,其中调整的Storyboard内容如下。
【范例7-7】 KFAnimationXY
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="AnimatedTransform"
Storyboard.TargetProperty="X"
Duration="0:0:10">
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="100" KeyTime="0:0:1" />
<LinearDoubleKeyFrame Value="200" KeyTime="0:0:2" />
<LinearDoubleKeyFrame Value="400" KeyTime="0:0:3" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="AnimatedTransform"
Storyboard.TargetProperty="Y"
Duration="0:0:10">
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="100" KeyTime="0:0:1" />
<LinearDoubleKeyFrame Value="200" KeyTime="0:0:2" />
<LinearDoubleKeyFrame Value="400" KeyTime="0:0:3" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
其中新增了第二个LinearDoubleKeyFrame标记,这个标记与第一个标记的内容完全相同,差异只在于Storyboard.TargetProperty这个属性设为Y,因此,它是沿着Y轴往下移动。我们将其合并第一个LinearDoubleKeyFrame标记,此次的矩形方块便会往右下角移动,如图7-7所示。
图7-7 矩形方块往右下角移动
通过合并数个KeyFrame相关标记,便能够让动画呈现更复杂的效果。
7.3.3 DiscreteDoubleKeyFrame
接下来介绍另外一种类型的内插法—— DiscreteDoubleKeyFrame,它与线性内插法最主要的差异,在于其输出值的改变是跳跃性的,只有在到达KeyTime所设定的时间点,输出值才会到达Value属性所指定的值,我们利用图7-8来说明。
图7-8 内插法比较
当我们通过Linear类型的KeyFrame设定动画时,在指定的Duration时间区间,动画内容是以线性的方式持续改变,而Discrete则刚好相反,它是一种跳跃式的动画,因此,在动画执行期间,所看到的内容是在指定的时间区间内瞬时达到其设定的目标值。
相较于Linear、Discrete类型的KeyFrame的语法并没有比较特别的地方,例如以下的设定值。
<DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<DiscreteDoubleKeyFrame Value="300" KeyTime="0:0:2" />
这两行XAML标记所定义的动画,会在第二个标记的KeyTime时间到达,直接输出Value属性所设定的300,在此之前,相关的值都会保持在0。换句话说,它是在到达指定时间点时,直接跳到300,而非逐步转换而成。
DiscreteDoubleKeyFrame与LinearDoubleKeyFrame除了标记名称不一样,以及运用的内插法不同之外,基本上,它们的设定语法是相同的,但是呈现的动画效果却有很大的差异。
【范例7-8】 DiscreteDoubleKeyFrame
现在进一步调整上述的范例,将原来的LinearDoubleKeyFrame修改为DiscreteDouble- KeyFrame,内容如下所示。
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="AnimatedTransform"
Storyboard.TargetProperty="X"
Duration="0:0:10">
<DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<DiscreteDoubleKeyFrame Value="100" KeyTime="0:0:1" />
<DiscreteDoubleKeyFrame Value="200" KeyTime="0:0:2" />
<DiscreteDoubleKeyFrame Value="400" KeyTime="0:0:3" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
为了节省篇幅,我们省略了一些内容,仅列举其中Storyboard的部分。这里使用DiscreteDoubleKeyFrame取代LinearDoubleKeyFrame,除此之外,XAML的内容均相同。
尝试播放这段动画,将得到如图7-9所示的结果。矩形方块只会在每个时间区段结束的同时,在Value指定的坐标位置出现。
DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0"
DiscreteDoubleKeyFrame Value="100" KeyTime="0:0:1"
DiscreteDoubleKeyFrame Value="200" KeyTime="0:0:2"
DiscreteDoubleKeyFrame Value="400" KeyTime="0:0:3"
图7-9 动画在每个时间区段结束时的效果
执行这个范例时,会看到以上的效果。当每一个DiscreteDoubleKeyFrame所设定的时间点到达时,画面上的蓝色矩形小方块便会跳跃至画面所显示的位置。
7.3.4 SplineDoubleKeyFrame
这一节,我们将介绍SplineDoubleKeyFrame。这个类型的KeyFrame提供用来制作更仿真的动画。在真实的世界,物体的移动位置随着时间的改变通常是非线性的,例如,水滴掉落是先慢后快。SplineDoubleKeyFrame以贝兹曲线定义动画改变的速率,因而能够得到更真实的动画效果。
不同于上述两种KeyFrame,它通过所谓的KeySpline指定所要建立的动画面效果,如下所示。
<SplineDoubleKeyFrame Value="300"
KeyTime="0:0:7"
KeySpline="0.0,1.0 1.0,0.0" />
其中的KeySpline代表形成贝兹曲线所需的两个控制点,定义非线性改变的动画速率,如图7-10所示。第一个控制点的位置在坐标点(0, 1),第二个控制点则是(1, 0)。
动画会在KeyTime所指定的时间点内完成动画的播放,而动画执行过程的速率,则以上述的贝兹曲线为依据,从曲线的起始点开始,弯曲度愈大时,动画播放的速率愈大,反之则愈小。因此,随着弯曲度的改变,动画的速率以非线性的方式跟着作调整,如图7-11所示。
贝兹曲线定义了动画速率改变的模式,用来定义SplineDoubleKeyFrame的贝兹曲线是一种立方贝兹曲线,可以通过两个控制点来决定曲线的弯曲程度,因此,间接决定了动画在进行中速率的改变方式。
图7-10 形成贝兹曲线所需的两个控制点
图7-11 动画的速率以非线性的方式跟着作调整
除了SplineDoubleKeyFrame的KeySpline属性定义之外,这种类型的KeyFrame与上述其他两种KeyFrame在语法的编写上并没有太大的差异,都必须定义事件、动画的目标属性以及目标对象,最重要的,在Storyboard中定义SplineDoubleKeyFrame。
以下是一个相关的范例。
【范例7-9】 SDKeyFrame
这个范例建立一个矩形对象,并且在指定的时间区间,改变矩形的X轴位置,所需的XAML如下所示。
<?xml version="1.0" encoding="utf-8" ?>
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="640" Height="480"
Background="White"
>
<Rectangle Fill="Blue"
Width="50" Height="50">
<Rectangle.RenderTransform>
<TranslateTransform x:Name="rct" X="0" Y="0" />
</Rectangle.RenderTransform>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="rct"
Storyboard.TargetProperty="X"
Duration="0:0:10"
RepeatBehavior="Forever">
<SplineDoubleKeyFrame Value="500"
KeyTime="0:0:10" KeySpline="0.0,1.0 1.0,0.0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</Canvas>
其中的TargetName指定为矩形的对象名称,TargetProperty则是它的X轴坐标位置,SplineDoubleKeyFrame标记定义了一个最终坐标点500,经过的时间区段为10秒。而最重要的KeySpline,它指定其对角的坐标点为控件点,因此,定义出一个对称的立方贝兹曲线,也就是前面我们所讨论的曲线图形。
对照上述的贝兹曲线,我们来看其执行结果,如图7-12所示。
图7-12 范例执行结果
由于曲线一开始非常陡峭,因此,矩形方块移动得非常快,当它移动至中间点,此时对应的贝兹曲线变得平坦,移动的速度开始慢下来,然后紧接着曲线又进入陡峭的爬升,矩形便加速移动至终点。
在这个范例中,我们看到了SplineDoubleKeyFrame的实际应用,也可以通过设定控制点,建立更复杂的动画效果。由于动画速率非线性改变,这种特性非常适合用来模拟真实世界的动画。
【范例7-10】 WaterDrop
这个范例运用了SplineDoubleKeyFrame的技巧,让画面上的球模拟水滴滴落的运动,从画面上方移动至下方,以下是此范例的XAML。
<?xml version="1.0" encoding="utf-8" ?>
<Canvas
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800" Height="600"
Background="White"
>
<Ellipse
Width="25" Height="25">
<Ellipse.Fill>
<LinearGradientBrush EndPoint="1,0.5" StartPoint="0,0.5">
<GradientStop Color="#FFEAEEF6" Offset="1"/>
<GradientStop Color="#FF0E8DAE" Offset="0"/>
</LinearGradientBrush>
</Ellipse.Fill>
<Ellipse.RenderTransform>
<TranslateTransform x:Name="ellipse" X="0" Y="0" />
</Ellipse.RenderTransform>
<Ellipse.Triggers>
<EventTrigger RoutedEvent="Ellipse.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimationUsingKeyFrames
Storyboard.TargetName="ellipse"
Storyboard.TargetProperty="Y"
Duration="0:0:6"
RepeatBehavior="Forever">
<SplineDoubleKeyFrame Value="400"
KeyTime="0:0:4" KeySpline="0.0,0.1 0.9,0.0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Ellipse.Triggers>
</Ellipse>
</Canvas>
这段XAML首先建立一个Ellipse对象,然后设定其渐层外观,紧接着是<Storyboard>,其中DoubleAnimationUsingKeyFrames的TargetProperty被设定为Y轴坐标,Duration长度为6秒钟,表示这个动画执行过程所改变的值是Y轴坐标。接下来,<SplineDoubleKeyFrame>标记的KeySpline包含用来定义速率的贝兹曲线控制点。当范例开始执行时,圆球在画面的最上方,然后缓缓掉落,并很快的到达最下方,如图7-13所示。
图7-13 圆球下落动画
从这个范例可以看出,SplineDoubleKeyFrame非常适合用来模拟真实世界的动画效果。搭配图形的转换,便能够制作出非常出色的动画。