如何使动画的持续时间动态化?

时间:2018-09-27 15:27:50

标签: c# .net wpf animation

假设我们有一个动画,当用户将矩形的宽度从 50更改为150 时,将矩形的宽度从 150更改为50 。远。让每个动画的持续时间为 1秒

如果用户将光标移到矩形上,然后等待1秒钟动画结束,然后将光标移离矩形,则一切正常。两个动画总共需要2秒钟,而它们的速度恰好是我们期望的速度。

但是,如果用户在第一个动画(50-> 150)完成之前将光标移开,则第二个动画的持续时间将为1秒,但其速度将非常慢。我的意思是,第二个动画将使矩形的宽度不是从 150到50 ,而是从 120到50 或从 70到50 您可以很快地将光标移开。但是同样的 1秒

所以我想了解的是如何使“向后”动画的持续时间保持动态。动态基于第一个动画的停止时间。或者,如果我将FromTo的值分别指定为150和50,Duration则指定为1秒,矩形宽度将为100 – WPF将自行计算动画已完成50%。 / p>

我正在测试使用动画的不同方式,例如触发器,样式中的EventTrigger和VisualStateGroups,但没有成功。

这里是显示我的问题的XAML示例。如果您想亲自查看我在说什么,请将光标移到矩形上,等待1-2秒(完成第一个动画),然后将光标移开。之后,将光标移到矩形上约0.25秒,然后将其移开。您会看到矩形的宽度变化非常缓慢。

<Rectangle Width="50"
           Height="100"
           HorizontalAlignment="Left"
           Fill="Black">
<Rectangle.Triggers>
    <EventTrigger RoutedEvent="MouseEnter">
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimation Storyboard.TargetProperty="Width"
                                 To="150"
                                 Duration="0:0:1" />
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
    <EventTrigger RoutedEvent="MouseLeave">
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimation Storyboard.TargetProperty="Width" />
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
</Rectangle.Triggers>

此外,我想解释一下我希望看到的计算结果。 因此,让我们想象一下另一个动画,该动画的时间从 100到1100 ,持续时间为 2秒。如果我们将其停止在 300 的位置,则必须开始回到 100 ,但不能持续2秒钟,而要恢复:

((300 - 100) / (1100 - 100)) * 2s = 0.4s

如果我们停在900,持续时间将为:

((900 - 100) / (1100 - 100)) * 2s = 1.6s

如果让动画正常完成,它将是:

((1100 - 100) / (1100 - 100)) * 2s = 2s

3 个答案:

答案 0 :(得分:0)

不幸的是,情节提要是经过编译的代码,这意味着您不能在元素中使用绑定来提高其功能。

我必须做类似的事情,而我发现达到此目的的唯一方法是访问故事板属性,该属性在我需要触摸时需要动态,并根据代码的需要设置该属性

因此触摸并从背后的代码中触发并不是理想的选择,但是嘿,用这种方式看,不是业务逻辑,它是严格的表示层,因此背后的代码通常是可以接受的做法。

因此,请在后面的代码中处理触发器,计算出数字,更新情节提要属性然后再启动它。目前,这可能是您的最佳选择。

我希望看到情节提要成为逻辑树的一部分,并具有依赖项属性来动态修改值,因此可以请求功能:)。

答案 1 :(得分:0)

WPF提供了编写自定义动画的可能性。

您可以使用MinSpeed属性创建一个:

public class MinSpeedDoubleAnimation : DoubleAnimation
{
    public static readonly DependencyProperty MinSpeedProperty =
        DependencyProperty.Register(
            nameof(MinSpeed), typeof(double?), typeof(MinSpeedDoubleAnimation));

    public double? MinSpeed
    {
        get { return (double?)GetValue(MinSpeedProperty); }
        set { SetValue(MinSpeedProperty, value); }
    }

    protected override Freezable CreateInstanceCore()
    {
        return new MinSpeedDoubleAnimation();
    }

    protected override double GetCurrentValueCore(
        double defaultOriginValue, double defaultDestinationValue,
        AnimationClock animationClock)
    {
        var destinationValue = To ?? defaultDestinationValue;
        var originValue = From ?? defaultOriginValue;
        var duration = Duration != Duration.Automatic ? Duration :
            animationClock.NaturalDuration;
        var speed = (destinationValue - originValue) / duration.TimeSpan.TotalSeconds;

        if (MinSpeed.HasValue && Math.Abs(speed) < MinSpeed)
        {
            speed = Math.Sign(speed) * MinSpeed.Value;
        }

        var value = originValue + speed * animationClock.CurrentTime.Value.TotalSeconds;

        return speed > 0 ?
            Math.Min(destinationValue, value) :
            Math.Max(destinationValue, value);
    }
}

并像这样使用它:

<EventTrigger RoutedEvent="MouseEnter">
    <BeginStoryboard>
        <Storyboard>
            <DoubleAnimation
                Storyboard.TargetProperty="Width"
                To="150" Duration="0:0:1" />
        </Storyboard>
    </BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
    <BeginStoryboard>
        <Storyboard>
            <local:MinSpeedDoubleAnimation
                Storyboard.TargetProperty="Width"
                To="50" MinSpeed="100"/>
        </Storyboard>
    </BeginStoryboard>
</EventTrigger>

为完整起见,这也是没有任何触发器和情节提要的最简单的代码隐藏解决方案:

<Rectangle Width="50" Height="100" HorizontalAlignment="Left" Fill="Black" 
           MouseEnter="RectangleMouseEnter" MouseLeave="RectangleMouseLeave"/>

使用这些事件处理程序:

private void RectangleMouseEnter(object sender, MouseEventArgs e)
{
    var element = (FrameworkElement)sender;
    var animation = new DoubleAnimation(150, TimeSpan.FromSeconds(1));
    element.BeginAnimation(FrameworkElement.WidthProperty, animation);
}

private void RectangleMouseLeave(object sender, MouseEventArgs e)
{
    var element = (FrameworkElement)sender;
    var duration = (element.ActualWidth - 50) / 100;
    var animation = new DoubleAnimation(50, TimeSpan.FromSeconds(duration));
    element.BeginAnimation(FrameworkElement.WidthProperty, animation);
}

答案 2 :(得分:0)

自定义动画的替代方法可以是附加属性,该属性绑定到目标元素的ActualWidth,可以动态调整动画的Duration

附加的属性可能如下所示:

public static class AnimationEx
{
    public static readonly DependencyProperty DynamicDurationProperty =
        DependencyProperty.RegisterAttached(
            "DynamicDuration", typeof(double), typeof(AnimationEx),
            new PropertyMetadata(DynamicDurationPropertyChanged));

    public static double GetDynamicDuration(DoubleAnimation animation)
    {
        return (double)animation.GetValue(DynamicDurationProperty);
    }

    public static void SetDynamicDuration(DoubleAnimation animation, double value)
    {
        animation.SetValue(DynamicDurationProperty, value);
    }

    private static void DynamicDurationPropertyChanged(
        DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var animation = o as DoubleAnimation;
        if (animation != null && animation.To.HasValue)
        {
            animation.Duration = TimeSpan.FromSeconds(
                Math.Abs((double)e.NewValue - animation.To.Value) / 100);
        }
    }
}

其中可能还有另一个速度因子附加属性,该属性硬编码为上面的100

该属性的用法如下:

<Rectangle.Triggers>
    <EventTrigger RoutedEvent="MouseEnter">
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimation Storyboard.TargetProperty="Width"
                                 To="150" Duration="0:0:1" />
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
    <EventTrigger RoutedEvent="MouseLeave">
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimation Storyboard.TargetProperty="Width" To="50"
                    local:AnimationEx.DynamicDuration="{Binding Path=ActualWidth,
                        RelativeSource={RelativeSource AncestorType=Rectangle}}" />
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
</Rectangle.Triggers>

一个可能的缺点是,当ActualWidth更改时,Duration会不断重置。但是,我无法通知任何视觉效果。