《Programming WPF》翻译 第8章 2.Timeline

Timeline代表了时间的延伸。它通常还描述了一个或多个在这段时间所发生的事情。例如,在前面章节描述的动画类型,都是Timeline。可哦率这样的DoubleAnimation

<DoubleAnimation From=”10” To=”300” Duration=”0:0:5” />

正如Duration属性指出的,这代表了一个5秒的时间长度。所有类型的Timeline总是有一个开始时间和一个持续时间。如果没有详细指定开始时间,它默认为0:0:0,但是它可以使用BeginTime属性设置。开始时间可以是相对于各种引用帧的,如当一个页面被解析的时候;或者是相对于另一个Timeline,依赖于Timeline在哪里定义的。

你还可以设置BeginTimenull。(在xaml中,这是通过{x:Null}标记来实现的。)这就指出了Timeline并没有一个固定的开始时间,但是可以被某个事件触发。后面我们将会看到任何触发一个Timeline

不仅表示一个特定的时间延伸,特定的timeline还表示一段时间内某个值的改变。在timeline的开始,值为10,在结束,值为300DoubleAnimation是很多内建动画类型的一个。

8.2.1 动画时间线类型

WPF提供了一组动画类——符合相同的基本样式。因此当你必须选择一个动画类型——这个类型匹配被设置了动画的属性类型,,动画类型的行为是相当一致的。

例如,Double类型的属性可以被设置动画——通过使用DoubleAnimation,而为了一个Color属性,你可以使用ColorAnimation。这些类型都允许遵循相同的TypeAnimation命名转换,正如你从表8-1中看到的。

Table 8-1. Animation types

BooleanAnimation

Int64Animation

SingleAnimation

ByteAnimation

MatrixAnimation

Size3DAnimation

CharAnimation

Point3DAnimation

SizeAnimation

ColorAnimation

PointAnimation

StringAnimation

DecimalAnimation

Rect3DAnimation

ThicknessAnimation

DoubleAnimation

RectAnimation

Vector3DAnimation

Int16Animation

Rotation3DAnimation

VectorAnimation

Int32Animation

   

所有的内建类提供了ToFrom属性来设置开始值和结束值。可选择的,很多还提供了By属性,这将允许你修改属性而不用知道它当前的值。如果示例8-4被应用到一个对象的Width,这将使得它增加100px的合理宽度,不管初始的Width值为多少。

示例8-4

<DoubleAnimation By="100" Duration="0:0:5" />

你可以设计动画为交叠的——通过开始一个在另一个结束之前。你甚至可以这么做,通过动画为同样的属性设置目标。如果动画使用了ToFrom,最后一个动画会覆盖其它的。但是如果动画使用了By,它们的效果是累积的。净结果是独立的动画效果的总和。

Tofrom属性在是示例8-1中所有的动画类型上都是有效的。(By属性不能够不是在所有的类型上都有效,因为有一些,如Color,这将没有任何意义。)当然,这些属性的类型匹配了ColorAnimation目标类型,这些属性将将会是Color类型的——当在DoubleAnimation上它们是Double类型的。在所有情形中,本质行为是一样的。动画简单地在其持续时间内添加了新值,从一个值到另一个值。

默认的,这种添加新值是线性的。这个值以常速在整个动画的持续时间内改变,然而,你可以通过AccelerationRationDecelerationRation属性来改变这个值。这些属性允许你向动画提供一个“软”的开始和结束。如果你设置了AccelerationRation0.2,这个动画的改变速度将要从0开始,它会逐渐地加速到全速,在timeline的第一个五分之一的持续时间里。如果你设置DecelerationRation0.1,动画将减速直到停止,在timeline的最后一个十分之一的持续时间里。

这是相当不寻常的——想只孤立地使用一个动画。你将经常想对多个相关的动画——一起工作以产生所需要的可视化效果——进行分类。为了支持这一点,timeline可以被分组和嵌套。

8.2.2 层次

Timeline经常排列在一个层次。我们已经看到SetterTimeline——作为DoubleAnimation的父一级,但这是普通的使更深层的嵌套,来管理更复杂的动画。我们使用ParallelTimeline来实现,这是一个timeline类型,作为分组其他timeline使用。

子一级Timeline的开始时间是相对于它们的父一级的。因此BeginTime0:1:0并不一定意味着1分钟。作为子一级Timeline,它意味着1分钟,在它的父一级开始之后。

示例8-5使用ParallelTimeline对一些动画进行分组。

示例8-5

<Window Text="TimelineHierarchy" Width="320" Height="100"
    xmlns
="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x
="http://schemas.microsoft.com/winfx/xaml/2005">

    
<Window.Storyboards>

        
<ParallelTimeline RepeatBehavior="Forever">

            
<SetterTimeline BeginTime="0:0:0" TargetName="button1"
                            Path
="(Button.Height)">
                
<DoubleAnimation Duration="0:0:0.2"
                                 By
="30" AutoReverse="True" />
            
</SetterTimeline>

            
<SetterTimeline BeginTime="0:0:1" TargetName="button2"
                            Path
="(Button.Height)">
                
<DoubleAnimation Duration="0:0:0.2"
                                 By
="30" AutoReverse="True" />
            
</SetterTimeline>


            
<ParallelTimeline BeginTime="0:0:2">
                
<SetterTimeline BeginTime="0:0:0" TargetName="button3"
                                Path
="(Button.Height)">
                    
<DoubleAnimation Duration="0:0:0.2"
                                     By
="30" AutoReverse="True" />
                
</SetterTimeline>
                
<SetterTimeline BeginTime="0:0:1" TargetName="button4"
                                Path
="(Button.Height)">
                    
<DoubleAnimation Duration="0:0:0.2"
                                     By
="30" AutoReverse="True" />
                
</SetterTimeline>
            
</ParallelTimeline>

        
</ParallelTimeline>

    
</Window.Storyboards>

    
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
        
<Button x:Name="button1" Height="25">One</Button>
        
<Button x:Name="button2" Height="25">Two</Button>
        
<Button x:Name="button3" Height="25">Three</Button>
        
<Button x:Name="button4" Height="25">Four</Button>
    
</StackPanel>
</Window>

这个动画按顺序修改了每个按钮的高度,放大了按钮,然后收缩到它的元素大小。图8-2显示了这个动画的中途的一个情形。

8-2


Storyboard的结构并不是像这个简单的序列所建议的那样直接。它有一点人为的结构,为了在层次上显示timeline的效果。每一个按钮都有一个SetterTimelineDoubleAnimation来为它的高度设置动画。前两个按钮是足够简单的,它们都是ParallelTimeline的子级,而且SetterTimeline.BeginTime属性被各自设置为0:0:00:0:1。这意味这第二个按钮伸展和缩短比第一个按钮晚1秒。然而,后两个按钮有点令人惊讶的。它们的BeginTime属性也都分别设置为0:0:00:0:1。虽然这样,它们并没有和头两个按钮同时伸展和缩短。图8-2显示了第四个按钮,和第二个按钮具有同样的大小。

这些按钮的动画从左到右一个接着一个执行。即使后两个按钮和前两个按钮有相同的BeginTime值,这仍然是可以工作的,原因是它们嵌入到了另一个ParallelTimeline中,这将轮流嵌入到顶级的ParallelTimeline中。后两个动画的BeginTime属性是关联到这个内嵌的ParallelTimeline,而不是顶级的ParallelTimeline。这种内嵌了ParallelTimeline的动画有一个值为0:0:2BeginTime,意味着它直到顶级timeline2秒后才开始运行,在前两个按钮被设置动画之后。这依次意味着这些内嵌按钮的动画直到这是才开始运行。

8-3说明了示例8-5Storyboard的结构。每一个timeline(包括SetterTimelineDoubleAnimation)都表示为一个水平线,在开始和结束的位置都有一个圆点。它的水平位置指出了,当timeline按照上面显示的刻度运行时,这个timeline向右显示的越远,,它运行的越晚。(这个刻度相对于应用程序开始的时间)

这种有层次的结构使得改变很容易——当一个动画序列开始时,而不用必须遍及这个序列的任意细节。因为每个BeginTime属性指向到它的父一级,我们可以通过调整这个单独的BeginTime来移动序列。例如,我们可以改变——当后两个按钮通过只改变它们父一级的BeginTime的方式设置动画。一种绘制的方式是想象在图8-3中通过一个被标记为BeginTime的垂直箭头获取这个结构。如果你移动一条线从一边到另一边,任何在这条线下的事物都会跟着移动。

图8-3

在这个示例中,唯一的
BeginTime——相对于流逝的时间,是顶级的不具备父一级的ParallelTimeline。默认的,顶级的ParallelTimeline会使用“全局应用程序时钟”作为它的参考。这个“全局应用程序时钟”开始运行于应用程序首次解析标记或加载xaml,因此任何这样timelineBeginTime是相对于应用程序首次加载UI的时间。

“全局应用程序时钟”并没有等第一个窗体的打开。当UI初始化的时候,它才开始运行。这意味着你的动画在显示这个窗体之前开始计时是可能的。极端的例子是,动画可以在窗体出现之前结束。如果你想动画只在窗体出现之前开始,你可以给它们一个空的BeginTime,以及使用在本章后面讨论的代码后置技术。我们希望这个样式的版本可以更容易地设置动画的开始时间——相对于UI的外观。

注意到在图8-3中,图表的右手边,所有的四个激活的timeline都到达了一个终点在一个严格相同的瞬间。这不仅仅是坐标。这甚至不是小心编码的结果,如果你看一下示例8-5,你可以看到,只有带着明确的延续时间的timeline才是DoubleAnimation元素。所有其它的timeline自动获取它们的延续时间。

8.2.3 延续时间

如果你没有提供一个Duration属性,timeline会尝试计算出它的延续时间。这会基于它的子级别的延续时间,设置它自己的延续时间,使之足够长以容纳任何最后一个结束的timeline

考虑一下示例8-6

示例8-6

<ParallelTimeline>

    
<SetterTimeline BeginTime="0:0:0" TargetName="button1"
                            Path
="(Button.Height)">
        
<DoubleAnimation Duration="0:0:0.2" By="30" />
    
</SetterTimeline>

    
<SetterTimeline BeginTime="0:0:1" TargetName="button2"
                            Path
="(Button.Height)">
        
<DoubleAnimation Duration="0:0:0.2" By="30" />
    
</SetterTimeline>

</ParallelTimeline>

每个DoubleAnimation都有一个显示的Duration,但是两个SetterTimeline元素没有。它们都有一个隐式的延续时间——由它们的子级DoubleAnimation结束的时间决定。在这个例子中,这意味着这两个SetterTimeline元素都有0.2秒的延续时间。

父一级ParallelTimeline是有趣的,因为它包括两个SetterTimeline元素,它们都有一个隐式的0.2秒延续时间。然而,这个timeline的有效延续时间并不是0.2秒;而是1.2秒。还记得一个隐式的延续时间并不简单的是最长的子级timeline的长度,而是由最后一个timeline结束的时间决定。第二个SetterTimeline对象的BeginTime值为0:0:1,也就是在它的父一级ParallelTimeline开始后1秒。由于这个子级的延续时间是0.2秒,它就直到它的父一级开始1.2后才会结束——意味着它的父一级有一个隐式的1.2秒延续时间。

所有的timeline都提供一个AutoReverse属性。如果被设为truetimeline将会反过来运行——在它到达终点时。这就加倍了它的延续时间。这会产生轻微地困惑,当与一个显示Duration协力工作时。一个带有显示0:0:0.2Duration以及AutoReverse设置为true,有一个有效的0.4秒延续时间。这就是为什么图8-3中的timeline都比你所希望的长一些。

一般而言,显示延续时间机制工作良好,可以为你节省一些努力。然而,有一些情形会引起惊讶。确实,它会引起一个轻微的小故障在一个早期的示例中。如果你测试了示例8-5,你会注意到这里有一个仅多于0.5秒的间隙在每个按钮伸展和收缩之间,除重复序列以外。在第四个按钮结束收缩和第一个按钮开始伸展之间没有间隙。这个小故障在图8-3中是可见的。 

你可以看到每个DoubleAnimation以一个整秒数在序列之间。第一个按钮马上就有了动画效果,第二个在1秒之后,第三个在2秒之后,第四个在3秒之后。但是因为这个动画会在3.4秒后重复,这引起了一个简单的不平衡的感觉。如果在4秒后重复,这将会更好。

有很多种方法来修复这个问题。我们可以仅设置顶级ParallelTimeline的延迟时间为4秒。更巧妙地,我们可以设置第四个SetterTimeline的延迟时间为1秒。这将隐式地扩展它的父一级ParallelTimeline2秒长——使得顶级ParallelTimeline4秒。尽管这个方法看上去不太直接,它避免了硬编码顶级timeline的延迟时间,意味着如果你后来添加了更多的子级动画,你不会需要返回来调整顶级的延迟时间。

8.2.4 循环

默认的,一个timeline开始于由它的BeginTime详细指定的偏移,并停止于当它到达延迟时间时。尽管如此,所有的timeline都有一个RepeatBehavior属性,支持它们重复一次或更多次在到达它们的终止点之后。

我们已经在示例8-5中看到这一点,在顶级ParallelTimelineRepeatBehavior设置为Forever之处。这有一个对顶级元素充分直接的意义:它们会在UI运行的时候重复。对于内嵌的timeline,这并不是非常简单的。当一个内嵌的带有RepeatBehavior设置为Forevertimeline到达延迟时间的终点时,它会回到起始点以及继续重复直到时间的终点,但是只为“the end of time”的小值。

记住任何嵌入timelineBeginTime都是相对于它的父一级。实际山,它的全部时间视图都由它的父一级决定。因此对于一个内嵌的timeline,“the end of time”意味着它的父一级的延迟时间。示例8-7显示了一个值为ForeverRepeatBehavior可以在一小段时间后被切分。

示例8-7

<Window Text="The End Of The World As We Know It" Width="330" Height="100"
    xmlns:x
="http://schemas.microsoft.com/winfx/xaml/2005"
    xmlns
="http://schemas.microsoft.com/winfx/avalon/2005">

    
<Window.Storyboards>
        
<ParallelTimeline Duration="0:0:5">
            
<SetterTimeline BeginTime="0:0:2" TargetName="button1"
                            Path
="(Button.Background).(SolidColorBrush.Color)">
                
<ColorAnimation From="Red" To="Yellow" Duration="0:0:1"
                                AutoReverse
="True"
                                RepeatBehavior
="Forever" />
            
</SetterTimeline>
        
</ParallelTimeline>
    
</Window.Storyboards>

    
<Button x:Name="button1" Background="Red" VerticalAlignment="Center"
            HorizontalAlignment
="Center">
        I feel fine
    
</Button>
</Window>

在这个示例中,按钮的背景被设置了动画效果:在红色和黄色之间渐变。它使用了一个ColorAnimation,带有一个值为ForeverRepeatBehavior。运行这段代码,在2秒内显示了一个红色的按钮,渐变到黄色,并返回来一次,再一次渐变到黄色,然后突然回到红色,并永远保持这样的方式。这2秒的延迟由SetterTimeline.BeginTime0:0:2导致。这个动画在一个半循环(3秒)后被切割,因为顶级的ParallelTimeline有一个显示的0:0:5延迟时间。一旦达到了这一点,timeline和所有它的子一级都会结束,动画也不再有效了,以及按钮反转到它的原始颜色。

8-4显示了示例8-7中的timeline结构。正如你看到的,SetterTimeline2秒后开始,因为它的BeginTime0:0:2ColorAnimation.Duration属性被设置为0:0:1,但是这并不是一个有效的延迟时间。首先,AutoReverse属性被设置为true,加倍了有效的长度。此外,因为它的RepeatBehavior值为Forever,它将会执行在它被允许的时候,因此它的有效的延迟时间只是被它的上下文约束。

SetterTimeline容器并没有一个显示的延迟时间,因此它获取不确定的有效的ColorAnimation延迟时间。但是这些都被它的父一级ParallelTimeline切割,带有它的显示5秒延迟时间。

如果你使用设置为ForeverRepeatBehavior,并没有在父一级进行切割——带有显示的延迟时间,隐式的父一级元素的延迟时间将是不确定的。从示例8-7中的ParallelTimeline移除Duration属性,允许颜色动画不确定地运行。

RepeatBehavior属性还支持有效的重复。你可以指示一个timeline来重复一个特定长度的时间或一个固定数量的迭代。示例8-8显示了这两个技术的例子。

8-4


示例
8-8

<ColorAnimation From="Red" To="Yellow" Duration="0:0:1"
                RepeatBehavior
="3x" />

<DoubleAnimation By="20"  Duration="0:0:0.25"
                 RepeatBehavior
="0:0:2" />

示例8-8中的ColorAnimationRepeatBehaior值为3x。这指出了动画应该重复3次然后停止。有效的动画结束延迟时间为3秒,三倍时间比没有使用重复的情况。DoubleAnimationRepeatBehavior值为0:0:2。这意味着这个动画将会重复直到2秒过后。

8.2.5 填充

很多动画有有限的延迟时间。这引发了一个问题:当动画完成后,被设置了动画的属性将会发生什么?到目前为止出现的示例都有点鬼鬼祟祟的——我们看到的所有动画或者是永远重复或者是设置属性回到它的原始值在它们结束之前。示例8-9没有使用这些诡计。

示例8-9

<Window x:Class="Holding.Window1" Text="Holding"
    xmlns
="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x
="http://schemas.microsoft.com/winfx/xaml/2005"
    Width
="320" Height="150">

    
<Window.Storyboards>
        
<SetterTimeline BeginTime="0:0:2" TargetName="myEllipse"
                        Path
="(Ellipse.Width)">
            
<DoubleAnimation From="10" To="300" Duration="0:0:5" />
        
</SetterTimeline>
    
</Window.Storyboards>

    
<StackPanel Orientation="Horizontal">
        
<Ellipse x:Name="myEllipse" Height="100" Fill="Red" />
    
</StackPanel>

</Window>

示例8-9非常类似于示例8-1。这两个示例都设置了动画,使椭圆的大小在5秒内从10增加到300。这里有5个不同点。示例8-9只运行了动画一次。它忽略了示例8-1中的RepeatBehavior。它还在开始之前等待2秒。

当你运行这个程序时,这个椭圆会初始化为不可见的。2秒后,它会出现,然后逐渐扩展——像之前那样。在5秒动画的终点,这个椭圆保持着它的原始大小。我们可以添加一些代码来更详细地看一下,正如示例8-10所示。

示例8-10

using System;
using System.Windows;
using System.Windows.Threading;
using System.Diagnostics;

namespace Holding
{
    
public partial class Window1 : Window
    
{
        
public Window1( )
        
{
            InitializeComponent( );

            t 
= new DispatcherTimer( );
            t.Tick 
+= new EventHandler(OnTimerTick);
            t.Interval 
= TimeSpan.FromSeconds(0.5);
            t.Start( );
            start 
= DateTime.Now;
        }

        
private DispatcherTimer t;
        
private DateTime start;

        
void OnTimerTick(object sender, EventArgs e)
        
{
            TimeSpan elapsedTime 
= DateTime.Now - start;
            Debug.WriteLine(elapsedTime.ToString( ) 
+ "" +
                myEllipse.Width);
        }

    }

}

示例8-10创建了一个timer,每秒2次调用我们的OnTimerTrick函数。(DispatcherTimer是一个特殊的WPF的计时器,保证了在能使UI安全工作的上下文中调用我们的计时器。这意味着我们不需要担心是否在安全的线程上。参见附录C获取更多WPF中线程的信息。)在每个计时器的tick中,椭圆的宽度将使用Debug类打印出来。运行这个程序在vs2008中,我们可以在“输出”面板中看到这些消息,如下:

    00:00:00.5007200: NaN
    00:00:01.1917136: NaN
    00:00:01.6924336: 19.4539942
    00:00:02.1931536: 48.57
    00:00:02.6938736: 77.512
    00:00:03.1945936: 106.628
    00:00:03.6953136: 135.57
    00:00:04.1960336: 164.628
    00:00:04.6967536: 193.686
    00:00:05.1974736: 222.686
    00:00:05.6981936: 251.744
    00:00:06.1989136: 280.802
    00:00:06.6996336: 300
    00:00:07.2003536: 300

这就说明了2点。首先,不要依赖DispatcherTimer特别精确于它什么时候回调的,尤其是如果你运行在调试器中。其次,在动画运行前,椭圆报到的准确宽度为NaN。这是Not a Number的简写,同时说明了属性的宽度并没有一个值。

NaN是由Double浮点类型支持的一个特殊值。对于WPF这不是稀奇的。IEEE标准中的浮点类型为积极的和消极的无限值定义了特殊值,以及这个“not a number”值。NaN经常出现于可疑的操作,如尝试00,或者从无限值中减去有限值。

虽然NaN是一个标准值,WPF在这里使用它有一点不寻常。它担当着一种标记值,指出了一个属性没有被设置。

我们不应惊讶于椭圆初始没有宽度,由于我们没有直接在标记中设置椭圆的宽度属性。我们使用动画间接地设置,因此Width属性只有一个意味深长的值,一旦动画开始。我们修复这个问题,通过设置椭圆的宽度。

<Ellipse x:Name=”myEllipse” Height=”100” Fill=”Red” Width=”42” />

做了这样的改动,这个椭圆在动画开始时是可见的。它初始化为42px宽度。(如以前,一旦动画结束,它是300px宽度。)调试器反映了这一点,在动画的开始显示了宽度为42的值,取代以NaN

    00:00:00.5007300: 42
00:00:01.0415184: 42
00:00:01.5422484: 42
00:00:02.0429784: 21.4259942
00:00:02.5537230: 50.948
00:00:03.0544530: 79.948
00:00:03.5551830: 109.006
00:00:04.0659276: 138.644
00:00:04.5666576: 167.7019942
00:00:05.0673876: 196.76
00:00:05.5781322: 226.34
00:00:06.0788622: 255.398
00:00:06.5795922: 284.398
00:00:07.0803222: 300
00:00:07.5810522: 300

这是动画的默认行为——当它们到达终点时,它们最后的值持续请求——只要它们的父一级timeline持续为活动的。在一些环境中,这可能并不总是你需要的行为,你可能想确保这个属性返回它的原始值。即使当它是你需要的行为的时候,这看起来并不是直接了当的。

当一个动画到达它的延迟时间的终点时,这个动画并没有完全结束。我们看到动画的最后值——应用到上面的示例中,原因是这个动画仍然是活动的,即使它已经到达延迟时间的终点。这个模糊的地带——在动画延迟时间的终点和它最后的钝化之间,被称为“填充周期”。

所有的timeline都有一个FillBehavior属性,详细指出了在timeline到达它的有效延迟时间之后发生了什么。默认值为HoldEnd,意味着这个动画将会继续应用它的最后值直到UI关闭,除非一些事引起它为无效的。可选择的FillBehavior,显示在示例8-11中,为Deactivate。这使得这个动画无效——一旦它到达了延迟时间的终点,意味着相应的属性将会反转它的值在动画开始之前。

示例8-11

<Ellipse x:Name="myEllipse" Height="100" Fill="Red" FillBehavior="Deactivate" />

注意到,不同于RepeatBehaviorFillBehavior属性对timeline的有效延迟时间没有影响。FillBehavior.HoldEnd只会做一些事——如果父一级timeline运行时间比正被讨论的timeline的延迟时间长。示例8-12显示了这样一个场景。父一级SetterTimeline10秒的延迟时间,当它的子一级有5秒的延迟时间,剩下一个5秒的“填充周期”。子一级FillBehavior并没有被设置,因此它默认为HoldEnd

示例8-12

<SetterTimeline TargetName="myEllipse" Path="(Ellipse.Width)"
                Duration
="0:0:10" HoldEnd="Deactivate">
    
<DoubleAnimation From="10" To="300" Duration="0:0:5" />
</SetterTimeline>


图8-5说明了这样一对timeline。由于父一级timelineFillBehaviorDeactivate,它在其自然的延迟时间终止点会失效。当父一级失效时,所有的子一级都会失效,因此这会引起子一级的“填充周期”到达终点,意味着相应的属性将全都回复到在动画开始之前的值。

8-5


如果顶级
timeline有一个默认为HoldEndFillBehavior,它的“填充周期”将是不确定的。这就一次意味着它的子一级“填充周期”也会是不确定的。示例8-13显示了这样一个有层次的Timeline。(这和示例8-9具有相同的一组Timeline

示例8-13

<Window.Storyboards>
    
<SetterTimeline BeginTime="0:0:2" TargetName="myEllipse"
                    Path
="(Ellipse.Width)">
        
<DoubleAnimation From="10" To="300" Duration="0:0:5" />
    
</SetterTimeline>
</Window.Storyboards>

这里,DoubleAnimationSetterTimeline都有一个显示的FillBehavior,因此它们默认为HoldEnd。由于SetterTimeline是顶级的timeline(没有父一级),这意味着它的“填充周期”是有效不确定的。这就依次表明DoubleAnimation也有一个不确定的“填充周期”,正如图8-6中的双向箭头指出的。示例8-13中带有Storyboard的结果是,椭圆的宽度从10增长到300,并保持在300

8-6


默认的填充行为意味着动画典型地结束于一个不确定的“填充周期”。这通常导致了渴望的行为:一旦动画的延迟时间结束一个动画的最后值就是在那个适当的的位置保持的值。然而,它有一个令人吃惊的结果——如果你一个接着一个应用多个动画到同样的属性,而且这些属性使用
By属性。例如,你可能有一个动画:在几秒内向右移动一个对象,接着另一个动画将会在它的“填充周期”内。这意味着两个动画是同时激活的。如果第二个动画使用了FromTo,这将复写第一个属性。但是如果它使用了By属性,这个动画将会累积。动画系统会把第二个动画的效果添加到第一个动画。

幸运的是,在这种情形中,这个行为的终结结果可以是你想要的——当使用By属性时,第二个动画的起始点是第一个动画的最后值。

8.2.6 速度

有时你可能发现你想改变动画运行的某部分的速度。对于一个简单的包含单独元素的动画,你可以只改变延迟时间。对于一个更复杂的动画,有很多timeline组成,这将变得冗长的

——来手动调整每个延迟时间。一个简单的解决方案是包装时间,有效地在任何timeline上使用SpeedRatio属性。

SpeedRatio允许你在动画向后播放处改变速率。它的默认值为1,意味着所有的timeline提前一秒——为实时流逝的每一秒。然而,如果你修改了若干timeline中一个SpeedRatio2,这个timeline以及它的子一级都会提前2秒——为实时流逝的每一秒。

SpeedRatio是相对于父一级timeline前进的速率,而不是绝对的流逝时间。这变得很重要——如果你在多个地方详细指出速率。示例8-14显示了一个示例8-5动画的修改后的版本,带有一个SpeedRatio属性,添加到某些timeline中。

示例8-14

<ParallelTimeline RepeatBehavior="Forever">

    
<SetterTimeline BeginTime="0:0:0" TargetName="button1"
                    Path
="(Button.Height)">
        
<DoubleAnimation Duration="0:0:0.2" By="30" AutoReverse="True" />
    
</SetterTimeline>

    
<SetterTimeline SpeedRatio="2" BeginTime="0:0:1" TargetName="button2"
                    Path
="(Button.Height)">
        
<DoubleAnimation Duration="0:0:0.2" By="30" AutoReverse="True" />
    
</SetterTimeline>


    
<ParallelTimeline BeginTime="0:0:2" SpeedRatio="4">
        
<SetterTimeline SpeedRatio="0.25" BeginTime="0:0:0" TargetName="button3"
                        Path
="(Button.Height)">
            
<DoubleAnimation Duration="0:0:0.2"
                             By
="30" AutoReverse="True" />
        
</SetterTimeline>
        
<SetterTimeline SpeedRatio="0.5" BeginTime="0:0:1" TargetName="button4"
                        Path
="(Button.Height)">
            
<DoubleAnimation SpeedRatio="0.125"  Duration="0:0:0.2"
                             By
="30" AutoReverse="True" />
        
</SetterTimeline>
    
</ParallelTimeline>
</ParallelTimeline>

图8-7显示了这些改动的效果。顶级timeline的速度没有详细指出,因此它默认为1,并按正常的速率前进。它的第一个SetterTimeline子一级也是这样的。第二个SetterTimelineSpeedRatio2。这没有影响这个timeline开始的时间。它的BeginTimeline是相对于它的父一级的,因此依赖于它的父一级速度。但是这个timeline的内容,DoubleAnimation,将会运行以正常速度的两倍,因此它就像是这个动画的延迟时间设置为0.1而不是0.2。结果是第二个按钮扩展和收缩在第一个按钮扩展和收缩的一半时间内。

顶级timeline的后两个子一级是一个ParalletTimeline元素,带有SpeedRatio4的属性。这将是4倍于它工作中的子一级timeline。可是,它的子一级是带有SpeedRatio0.25属性的SetterTimeline。因此,这个timeline——设置在第三个按钮的动画,将会以正常的速度运行。下一个内嵌的SetterTimeline——控制着第四个按钮,它的BeginTime设置为0:0:1,但是因为它的父一级SpeedRatio4,它将会开始于这个timeline中的1/4秒,引起它轻微交叠于前一个动画,如图8-7所示。它的速度为0.5,但这是相对于它的父一级速度为4,意味着这个timeline以双倍速度运行。可是,它的子一级是速度为0.125DoubleAnimation。这里有3SpeedRatio值在运行中。这里,内嵌的ParalletTimelineSetterTimelineDoubleAnimation的速度分别为40.50.125。联合这些,我们得到了0.25。因此最后的结果是第四个按钮的动画效果为四分之一的正常速度,因此是延迟时间的4倍。

8-7


到目前为止,本章所有的示例,你可能想知道为什么这些动画都在
storyboard中。这是可论证地,对于ColorAnimation更加简单,以间接地嵌入到示例8-2中的SolidColorBrush,取代以被隔离到Window.Storyboard属性中,这里需要一个SetterTimeline元素来指出它应用到哪个元素。对于非常简单的动画,使用storyboard可能有一点麻烦,但是一旦你想要同时为多个属性设置动画,就会保持这些动画异步激活的挑战。Storyboard存在以解决这个问题。

posted @ 2008-04-03 23:03  包建强  Views(993)  Comments(1Edit  收藏  举报