Как сделать продолжительность анимации динамичной? - PullRequest
0 голосов
/ 27 сентября 2018

Давайте представим, что у нас есть анимация, которая изменяет ширину прямоугольника с 50 до 150 , когда пользователь перемещает курсор над ним, и обратно с 150 до 50 , когда пользователь перемещает курсор в сторону.Пусть продолжительность каждой анимации будет 1 секунда .

Все в порядке, если пользователь наводит курсор на прямоугольник, затем ждет 1 секунду для завершения анимации, а затем перемещает курсор в сторонуиз прямоугольника.Две анимации занимают всего 2 секунды, и их скорость соответствует ожидаемой.

Но если пользователь отодвинет курсор до завершения первой анимации (50 -> 150), продолжительность второй анимации будет равна1 секунда, но скорость будет очень медленной.Я имею в виду, что вторая анимация будет анимировать ширину прямоугольника не от 150 до 50 , а, скажем, от 120 до 50 или от 70 до 50 , если вы возьмете курсорочень быстроНо для той же 1 секунды !

Итак, я хочу понять, как сделать так, чтобы продолжительность анимации «назад» была динамической .Динамический в зависимости от того, в какой момент первая анимация была остановлена.Или, если я укажу значения From и To для 150 и 50, от Duration до 1 секунды, а ширина прямоугольника будет равна 100 - WPF сама рассчитает, что анимация выполнена на 50%.

Iтестировал различные способы использования анимации, такие как 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

Ответы [ 3 ]

0 голосов
/ 27 сентября 2018

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);
}
0 голосов
/ 28 сентября 2018

Альтернативой пользовательской анимации может быть прикрепленное свойство, связанное с целевым элементом 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>

Возможным недостатком может быть тот факт, что Duration постоянно сбрасывается при изменении ActualWidth.Однако я не смог уведомить о каком-либо визуальном эффекте.

0 голосов
/ 27 сентября 2018

К сожалению, раскадровки - это скомпилированный код, что означает, что вы не можете использовать связывание в элементах для улучшения их возможностей.

Мне пришлось сделать что-то похожее, и единственный способ, который я нашел для этого, - этополучить доступ к свойству раскадровки, которое должно было быть динамическим в то время, когда мне нужно было прикоснуться к нему, и установить это свойство в коде, как это необходимо.

Так что не является идеальным касаться и запускать из кода позади, ноэй, посмотри на это так, это не бизнес-логика, это строго уровень представления, поэтому код обычно является приемлемой практикой.

Так что обрабатывай свой триггер в коде позади, вычисляй свои числа, обновляй свойства раскадровки и затем запускай его,Это, вероятно, будет вашим лучшим вариантом на данный момент.

Я хотел бы, чтобы раскадровки стали частью логического дерева со свойствами зависимостей для динамического изменения значений, поэтому, возможно, запросите функцию:).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...