对滚动条或滑块的位置进行动画处理将冻结它

如果使用一个 FillBehavior HoldEnd (默认值)的动画对滚动条或滑块的位置进行动画处理,则用户将无法再移动该滚动条或滑块。 这是因为,即使动画已结束,它仍然在重写目标属性的基值。 若要使动画不再替代该属性的当前值,请删除它,或为其赋予 Stop FillBehavior 。 有关详细信息及示例,请参阅 在使用情节提要对属性进行动画处理后设置该属性

对动画的输出进行动画处理没有效果

如果某个对象是另一个动画的输出,则无法对该对象进行动画处理。 例如,如果使用 ObjectAnimationUsingKeyFrames 对某个 Rectangle Fill 进行从 RadialGradientBrush SolidColorBrush 的动画处理,则无法对 RadialGradientBrush SolidColorBrush 的任何属性进行动画处理。

在对属性进行动画处理后无法更改该属性的值

在某些情况下,在对属性进行动画处理后,即使在动画结束后,看起来仍无法更改该属性的值。 这是因为即使动画已结束,它仍然在重写该属性的基值。 若要使动画不再替代该属性的当前值,请删除它,或为其赋予 Stop FillBehavior 。 有关详细信息及示例,请参阅 在使用情节提要对属性进行动画处理后设置该属性

更改时间线没有效果

尽管大多数 Timeline 属性都可以进行动画处理和数据绑定,但更改活动 Timeline 的属性值似乎没有效果。 这是因为,当 Timeline 开始时,计时系统将创建 Timeline 的副本并使用它创建 Clock 对象。 修改原件对系统的副本没有影响。

若要使 Timeline 反映更改,必须重新生成它的时钟,并用它来替换以前创建的时钟。 时钟不会自动重新生成。 以下是应用时间线更改的几种方法:

  • 如果时间线是 Storyboard 或属于它,则可以通过使用 BeginStoryboard Begin 方法重新应用其情节提要来使其反映更改。 这还会产生重新启动动画的附带影响。 在代码中,可以使用 Seek 方法将情节提要向前移回其之前的位置。

  • 如果已使用 BeginAnimation 方法将动画直接应用到某个属性,则再次调用 BeginAnimation 方法并向其传递已修改的动画。

  • 如果要直接在时钟级别上工作,请创建并应用一组新的时钟,然后用它们替换之前生成的一组时钟。

    有关时间线和时钟的详细信息,请参阅 动画和计时系统概述

    FillBehavior.Stop 不按预期方式工作

    有时,将 FillBehavior 属性设置为 Stop 似乎没有效果,例如当一个动画“切换”到另一个动画时,由于它具有 SnapshotAndReplace HandoffBehavior 设置而没有效果。

    下面的示例将创建一个 Canvas Rectangle TranslateTransform 。 将对 TranslateTransform 进行动画处理,以使 Rectangle 围绕 Canvas 移动。

    <Canvas Width="600" Height="200">
      <Rectangle 
        Canvas.Top="50" Canvas.Left="0" 
        Width="50" Height="50" Fill="Red">
        <Rectangle.RenderTransform>
          <TranslateTransform 
            x:Name="MyTranslateTransform" 
            X="0" Y="0" />
        </Rectangle.RenderTransform>
      </Rectangle>
    </Canvas>
    

    本部分中的示例使用上述对象演示 FillBehavior 属性行为不符合预期的几种情况。

    针对多个动画的 FillBehavior="Stop" 和 HandoffBehavior

    有时,当某个动画替换为第二个动画时,似乎会忽略其 FillBehavior 属性。 以下面的示例为例,该示例创建两个 Storyboard 对象并使用它们对上述示例中所示的相同 TranslateTransform 进行动画处理。

    第一个 StoryboardB1TranslateTransformX 属性进行从 0 到 350 的动画处理,这会将矩形向右移动 350 像素。 当动画到达其持续时间的末尾并停止播放时,X 属性会恢复为其原始值 0。 因此,矩形向右移动 350 像素,然后跳回其原始位置。

    <Button Content="Start Storyboard B1">
      <Button.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
          <BeginStoryboard>
            <Storyboard x:Name="B1">
              <DoubleAnimation 
                Storyboard.TargetName="MyTranslateTransform"
                Storyboard.TargetProperty="X"
                From="0" To="350" Duration="0:0:5"
                FillBehavior="Stop"
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger>
      </Button.Triggers>
    </Button>
    

    第二个 StoryboardB2 也对相同 TranslateTransformX 属性进行动画处理。 由于仅设置了此 Storyboard 中动画的 To 属性,因此动画使用它进行动画处理的属性的当前值作为其起始值。

    <!-- Animates the same object and property as the preceding Storyboard. --> <Button Content="Start Storyboard B2"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard x:Name="B2"> <DoubleAnimation Storyboard.TargetName="MyTranslateTransform" Storyboard.TargetProperty="X" To="500" Duration="0:0:5" FillBehavior="Stop" /> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button>

    如果在第一个 Storyboard 播放时单击第二个按钮,用户可能看到以下行为:

  • 第一个情节提要结束并将矩形发送回其原始位置,因为动画的 FillBehaviorStop

  • 第二个情节提要生效,从当前位置(现在为 0)播放动画到 500。

    但情况并非如此。 矩形没有跳回,而是继续向右移动。 这是因为第二个动画使用第一个动画的当前值作为其起始值,并从该值开始播放动画到 500。 当第二个动画因为使用了 SnapshotAndReplaceHandoffBehavior 而取代了第一个动画时,第一个动画的 FillBehavior 无关紧要。

    FillBehavior 和 Completed 事件

    下面的示例演示了 StopFillBehavior 看似没有任何效果的另一种情况。 同样,该示例使用情节提要对 TranslateTransformX 属性进行从 0 到 350 的动画处理。 但此次,该示例注册了 Completed 事件。

    <Button Content="Start Storyboard C">
      <Button.Triggers>
        <EventTrigger RoutedEvent="Button.Click">
          <BeginStoryboard>
            <Storyboard Completed="StoryboardC_Completed">
              <DoubleAnimation 
                Storyboard.TargetName="MyTranslateTransform"
                Storyboard.TargetProperty="X"
                From="0" To="350" Duration="0:0:5"
                FillBehavior="Stop" />
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger>
      </Button.Triggers>
    </Button>
    

    Completed 事件处理程序启动另一个 Storyboard,对相同的属性进行从其当前值到 500 的动画处理。

    private void StoryboardC_Completed(object sender, EventArgs e)
        Storyboard translationAnimationStoryboard =
            (Storyboard)this.Resources["TranslationAnimationStoryboardResource"];
        translationAnimationStoryboard.Begin(this);
    
    Private Sub StoryboardC_Completed(ByVal sender As Object, ByVal e As EventArgs)
        Dim translationAnimationStoryboard As Storyboard = CType(Me.Resources("TranslationAnimationStoryboardResource"), Storyboard)
        translationAnimationStoryboard.Begin(Me)
    End Sub
    

    以下是将第二个 Storyboard 定义为资源的标记。

    <Page.Resources>
      <Storyboard x:Key="TranslationAnimationStoryboardResource">
        <DoubleAnimation 
          Storyboard.TargetName="MyTranslateTransform"
          Storyboard.TargetProperty="X"
          To="500" Duration="0:0:5" />
      </Storyboard>
    </Page.Resources>
    

    运行 Storyboard 时,可能希望 TranslateTransformX 属性进行从 0 到 350 的动画处理,完成后再还原到 0(因为其 FillBehavior 设置为 Stop),然后进行从 0 到 500 的动画处理。 而 TranslateTransform 则进行从 0 到 350,然后到 500 的动画处理。

    这是因为 WPF 引发事件的顺序,也因为属性值已缓存,除非该属性失效,否则不会重新计算。 首先处理 Completed 事件,因为该事件是由根时间线(第一个 Storyboard)触发。 此时,X 属性仍然返回其经过动画处理后的值,因为它尚未失效。 第二个 Storyboard 使用缓存的值作为其起始值并开始进行动画处理。

    在导航离开页面后动画继续运行

    当用户导航离开包含正在运行的动画的 Page 后,这些动画将继续播放,直到对 Page 进行垃圾回收。 根据正在使用的导航系统,导航离开的页面可能无限期地保留在内存中,在此期间始终通过动画消耗资源。 当页面包含不断运行的(“氛围”)动画时,这一点最明显。

    出于此原因,在导航离开页面时,最好使用 Unloaded 事件删除动画。

    删除动画有多种不同的方法。 以下技术可用于删除属于 Storyboard 的动画。

  • 若要删除通过事件触发器启动的 Storyboard,请参阅如何:删除情节提要

  • 若要使用代码删除 Storyboard,请参阅 Remove 方法。

    无论动画如何启动,都可以使用下一个技术。

  • 要从特定属性中移除动画,请使用 BeginAnimation(DependencyProperty, AnimationTimeline) 方法。 将正在进行动画处理的属性指定为第一个参数,并将 null 指定为第二个参数。 这将从该属性中删除所有动画时钟。
  • 有关对属性进行动画处理的不同方法的详细信息,请参阅属性动画技术概述

    使用组合 HandoffBehavior 会消耗系统资源

    当使用 ComposeHandoffBehaviorStoryboardAnimationTimelineAnimationClock 应用于属性时,之前与该属性关联的任何 Clock 对象都会继续消耗系统资源;计时系统不会自动删除这些时钟。

    为避免在使用 Compose 应用大量时钟时出现性能问题,应该在完成后从经动画处理过的属性中删除组合时钟。 删除时钟有多种方法。

  • 要从属性中删除所有时钟,请使用经动画处理的对象的 ApplyAnimationClock(DependencyProperty, AnimationClock)BeginAnimation(DependencyProperty, AnimationTimeline) 方法。 将正在进行动画处理的属性指定为第一个参数,并将 null 指定为第二个参数。 这将从该属性中删除所有动画时钟。

  • 要从时钟列表中删除特定 AnimationClock,请使用 AnimationClockController 属性来检索 ClockController,然后调用 ClockControllerRemove 方法。 这通常在某个时钟的 Completed 事件处理程序中完成。 注意,ClockController 只能控制根时钟,子时钟的 Controller 属性将返回 null。 另请注意,如果时钟的有效持续时间是永远,将不会调用 Completed 事件。 在这种情况下,用户需要确定何时调用 Remove

    此动画问题主要出现在生存期较长的对象上。 当对某个对象进行垃圾回收时,它的时钟也会断开连接并进行垃圾回收。

    有关时钟对象的详细信息,请参阅动画和计时系统概述

  •