移除故事板但保留动画数值?

10

如何在XAML中删除一个Storyboard(例如,在DataTrigger中使用RemoveStoryboard操作),但保留被动画化的值。与Animatable.BeginAnimation类似:

如果动画的BeginTime为null,则会移除任何当前的动画并保留属性的当前值。

2个回答

4
RemoveStoryboard的主要用途是删除动画值并将其设置回未动画状态。在大多数情况下,您可以根据具体情况切换到PauseStoryboard或StopStoryboard调用。唯一的例外是当您需要释放故事板持有的资源或将其用于其他目的时。
如果您真的想删除故事板并保留属性值,则必须直接在属性上设置动画值。这可以通过将每个值设置为动画值来完成,类似于以下内容:
void CopyAnimatedValuesToLocalValues(DependencyObject obj)
{
  // Recurse down tree
  for(int i=0; i<VisualTreeHelper.GetChildrenCount(obj); i++)
    CopyAnimatedValuesToLocalValues(VisualTreeHelper.GetChild(obj, i));

  var enumerator = obj.GetLocalValueEnumerator();
  while(enumerator.MoveNext())
  {
    var prop = enumerator.Current.Property;
    var value = enumerator.Current.Value as Freezable;

    // Recurse into eg. brushes that may be set by storyboard, as long as they aren't frozen
    if(value!=null && !value.IsFrozen)
      CopyAnimatedValuesToLocalValues(value);

    // *** This is the key bit of code ***
    if(DependencyPropertyHelper.GetValueSource(obj, prop).IsAnimated)
      obj.SetValue(prop, obj.GetValue(prop));

  }
}

在删除故事板之前,请调用此方法以复制动画值。
编辑:有人评论说,这段代码可能是不必要的,因为使用BeginTime=null调用BeginAnimation可以实现类似的效果。
虽然使用BeginTime=null调用BeginAnimation看起来就像将值复制到本地一样,但稍后调用RemoveStoryboard将导致值恢复。这是因为BeginTime=null的BeginAnimation使先前的动画保留其值,直到新动画开始,但不会影响本地值。
上面的代码实际上覆盖了本地值,因此可以删除所有动画,对象仍将具有其新值。因此,如果您真的想调用RemoveStoryboard并仍保留自己的值,则需要我写的代码或类似的代码。

正如我在问题中提到的,我想在XAML中完成它。这里的主要目标是基于DataTrigger开始一个新的动画,该动画从当前值开始。据我所知,无法通过暂停或停止当前的Storyboard来实现,因为它会保持或重置当前值。 另外请注意,在WPF中不需要您的代码。您只需调用具有BeginTime设置为null的动画的Animatable.BeginAnimation即可。它将产生相同的效果,该值将被复制到本地。 - bitbonk
我不确定我理解你的需求。如果你只是想开始一个新的动画,为什么不直接这样做,而不调用RemoveAnimation操作呢?在这种情况下,WPF被设计为从当前值开始。它没有这样做吗? - Ray Burns
调用BeginTime=null的BeginAnimation并不像我的代码一样产生相同的效果。具体来说,它不会将值复制到本地。如果您使用BeginTime=null调用BeginAnimation,然后稍后调用RemoveAnimation,则值将恢复为原始值,因为本地值从未更改。但是,如果您使用我的代码,则值将保持不变,因为它们实际上被复制到本地。我解释了如何使本地值即使在RemoveAnimation之后也能保持不变,而像我编写的代码确实是必要的。这有意义吗? - Ray Burns
你说得对,它们不一样。关于你的问题:如果我在开始新的Storyboard之前删除一个Storyboard,动画值会跳回默认(未动画化)值。我不希望发生这种情况,我想从其他动画停止的值开始。此外,我需要一个仅限XAML的解决方案。但是使用DataTrigger(似乎是基于ViewModel状态触发动画的唯一方法),我总是必须在DataTrigger.ExitAction中删除旧动画。 - bitbonk
1
让我们做一个看起来很难实现的简单练习:编写一个数据模板,当绑定的ViewModel状态变为“active”时,它会缓慢地将其颜色动画化为绿色。当状态变为“inactive”时,它会从当前颜色开始将其颜色动画化为灰色。当状态变为“unknown”时,开始将其颜色动画化为黄色,同样从对象当前具有的任何颜色开始。这是一个非常简单的场景,但确实让我很困扰。我倾向于认为这就是WPF动画系统的不足之处。 - bitbonk

1

我在使用AnimationTimeline时遇到了类似的问题。最简单的解决方案是在代码后台捕获Completed事件,并在调用null参数之前调用BeginAnimation以删除动画,获取属性的当前值并将其用于设置。

这将获取最后一个动画值并将其设置。

void OnCompleted( object sender, EventArgs args )
{
    // Required to copy latest animated value to local value.
    o.SomeValue = o.SomeValue;
    o.BeginAnimation( SomeClass.SomeValueProperty, null );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接