[英]How to make the duration of an animation dynamic?
讓我們假設我們有一個動畫,當用戶將光標移到它上面時,將矩形的寬度從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%。
我正在測試使用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.