簡體   English   中英

如何使動畫的持續時間動態?

[英]How to make the duration of an animation dynamic?

讓我們假設我們有一個動畫,當用戶將光標移到它上面時,將矩形的寬度從50改為150,當用戶將光標移開時,將寬度從150改為50 讓每個動畫的持續時間為1秒

如果用戶將光標移動到矩形上,則等待1秒鍾以完成動畫,然后將光標移離矩形。 兩部動畫完全耗時2秒,速度正是我們所期望的。

但是如果用戶在第一個動畫(50 - > 150)完成之前將光標移開,則第二個動畫的持續時間將為1秒,但速度將非常慢。 我的意思是,第二個動畫將動畫矩形的寬度不是從150到50,但是從120到5070到50,如果你把光標拉得很快。 但是同樣的1秒

所以我想要了解的是如何使“向后”動畫的持續時間變得動態 動態基於第一個動畫停止的位置。 或者,如果我將FromTo值指定為150和50,則Duration為1秒,矩形寬度將為100 - WPF將自行計算動畫完成50%。

我正在測試使用Triggers,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

不幸的是,Storyboard是編譯代碼,意味着你不能在元素中使用綁定來提高他們的能力。

我必須做類似的事情,我發現實現它的唯一方法是訪問我需要觸摸它時需要動態的storyboard屬性,並在后面的代碼中根據需要設置屬性。

因此,不得不從代碼中觸摸和觸發它,但是嘿以這種方式看待它,它不是業務邏輯,它是嚴格的表示層,因此代碼隱藏通常是可接受的實踐。

因此,在后面的代碼中處理您的觸發器,計算您的數字,更新故事板屬性然后啟動它。 這可能是你現在最好的選擇。

我很樂意看到故事板成為具有依賴屬性的邏輯樹的一部分,以動態修改值,因此可能請求功能:)。

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);
}

自定義動畫的替代方法可以是綁定到目標元素的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發生變化時,持續時間會不斷重置。 但是,我無法通知任何視覺效果。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM