由于FillBehavior.HoldEnd,上一个动画正在阻止后面的动画(或动画属性)。
另一个错误是您操作Frame.Margin 属性的方式。
请注意,如果您想向左滑出ThicknessAnimation,则应将Margin 从0 设置为-50,0,0,0(而不是0,0,50,0)。
这是因为正面的FrameworkElement.Margin 只会影响周围的元素,而不会影响实际的元素。这意味着因为Frame 右侧没有相邻元素,所以您看不到任何效果(否则右侧相邻元素将被向右推 50 像素)。
要影响Frame,您必须使用否定 Margin 来“拖动”它。
推荐的方法是为 TranslateTransform 设置动画,而不是边距的厚度。
另请注意,动画Frame 本身可能不是最好的解决方案。如果您在Frame 上显示导航控件,那么看到所有东西进出会看起来很奇怪。您应该直接为Frame.Content 设置动画。
版本 1(推荐)
DataTrigger 基于属性的状态。它由状态更改触发并执行DataTrigger.EnterActions。一旦状态回落到原始状态,DataTrigger.ExitActions 将被执行。进入动画(使用FillBehavior.HoldEnd)持有的“属性值锁定”不会影响退出动画。
您应该利用DataTrigger 的自动状态跟踪,并将第二个滑入动画移动到DataTrigger.ExitActions 集合中。
在以下示例中,Frame.ContentTemplate 用于动画内容,而不是 Frame 本身。此示例使用DoubleAnimation 来为框架的ContentControl.RenderTransform 属性(设置为TranslateTransform)设置动画,而不是ThicknessAnimation:
触发动画
private async void ChangePage_OnButtonClick(object sender, RoutedEventArgs e)
{
// Slide current Frame.Content out to the left
AnimateFrameOutToLeft = true;
await Task.Delay(5000);
// Slide new Frame.Content in from left to right left
AnimateFrameOutToLeft = false;
}
动画定义
<Frame x:Name="Frame" NavigationUIVisibility="Visible">
<Frame.ContentTemplate>
<ItemContainerTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform />
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding AnimateFrameOutToLeft}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)"
Duration="0:0:0.25"
To="-50"
FillBehavior="HoldEnd" />
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Duration="0:0:0.25"
From="1" To="0"
FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)"
Duration="0:0:0.25"
To="0"
FillBehavior="HoldEnd" />
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Duration="0:0:0.25"
From="0" To="1"
FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</ItemContainerTemplate>
</Frame.ContentTemplate>
</Frame>
版本 2
属性Frame.Margin 和Frame.Opacity 仍被上一个动画阻止,该动画由于FillBehavior.HoldEnd 设置仍在执行。
在执行时间轴中的下一个动画之前,您必须停止上一个动画。
给起始 BeginStoryboard 一个名字,例如SlideOutStoryboard。然后在滑入式触发器的EnterActions 集合中添加一个StopStoryboard,它以前BeginStoryboard 为目标:
<Frame.Style>
<Style TargetType="{x:Type Frame}">
<Style.Triggers>
<DataTrigger Binding="{Binding AnimateFrameOutToLeft}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="SlideOutStoryboard">
<Storyboard>
<ThicknessAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Margin"
From="0" To="-50,0,0,0"
FillBehavior="HoldEnd" />
<DoubleAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Opacity"
From="1" To="0"
FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding AnimateFrameInToLeft}" Value="True">
<DataTrigger.EnterActions>
<!-- Stop the previous BeginStoryBoard "SlideOutStoryboard" -->
<StopStoryboard BeginStoryboardName="SlideOutStoryboard" />
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Margin"
From="-50,0,0,0" To="0,0,0,0"
FillBehavior="HoldEnd" />
<DoubleAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Opacity"
From="0" To="1"
FillBehavior="HoldEnd" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Frame.Style>
版本 3
这是一个扩展 Frame 的实现。它与标准的Frame 一样使用,只是它会在导航时自动为Content 设置动画(或Content 更改):
public class AnimatedFrame : Frame
{
private bool IsAnimating { get; set; }
private UIElement NextContent { get; set; }
private UIElement PreviousContent { get; set; }
private Action PreviousContentTransformCleanupDelegate { get; set; }
private Action NextContentTransformCleanupDelegate { get; set; }
public AnimatedFrame() => this.Navigating += OnNavigating;
private void OnNavigating(object sender, NavigatingCancelEventArgs e)
{
if (this.IsAnimating
|| !(e.Content is UIElement nextContent
&& this.Content is UIElement))
{
return;
}
e.Cancel = true;
this.PreviousContent = this.Content as UIElement;
this.NextContent = nextContent;
AnimateToNextContent();
}
private void AnimateToNextContent()
{
PrepareAnimation();
StartPreviousContentAnimation();
}
private void PrepareAnimation()
{
this.IsAnimating = true;
Transform originalPreviousContentTransform = this.PreviousContent.RenderTransform;
this.PreviousContent.RenderTransform = new TranslateTransform(0, 0);
this.PreviousContentTransformCleanupDelegate =
() => this.PreviousContent.RenderTransform = originalPreviousContentTransform;
Transform originalNextContentTransform = this.NextContent.RenderTransform;
this.NextContent.RenderTransform = new TranslateTransform(0, 0);
this.NextContentTransformCleanupDelegate = () => this.NextContent.RenderTransform = originalNextContentTransform;
}
private void StartPreviousContentAnimation()
{
var unloadAnimation = new Storyboard();
DoubleAnimation slideOutAnimation = CreateSlideOutAnimation();
unloadAnimation.Children.Add(slideOutAnimation);
DoubleAnimation fadeOutAnimation = CreateFadeOutAnimation();
unloadAnimation.Children.Add(fadeOutAnimation);
unloadAnimation.Completed += StartNextContentAnimation_OnPreviousContentAnimationCompleted;
unloadAnimation.Begin();
}
private void StartNextContentAnimation_OnPreviousContentAnimationCompleted(object sender, EventArgs e)
{
this.Content = this.NextContent;
var loadAnimation = new Storyboard();
DoubleAnimation slideInAnimation = CreateSlideInAnimation();
loadAnimation.Children.Add(slideInAnimation);
DoubleAnimation fadeInAnimation = CreateFadeInAnimation();
loadAnimation.Children.Add(fadeInAnimation);
loadAnimation.Completed += Cleanup_OnAnimationsCompleted;
loadAnimation.Begin();
}
private void Cleanup_OnAnimationsCompleted(object sender, EventArgs e)
{
this.PreviousContentTransformCleanupDelegate.Invoke();
this.NextContentTransformCleanupDelegate.Invoke();
this.IsAnimating = false;
}
private DoubleAnimation CreateFadeOutAnimation()
{
var fadeOutAnimation = new DoubleAnimation(1, 0, new Duration(TimeSpan.FromMilliseconds(250)), FillBehavior.HoldEnd)
{BeginTime = TimeSpan.Zero};
Storyboard.SetTarget(fadeOutAnimation, this.PreviousContent);
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(nameof(UIElement.Opacity)));
return fadeOutAnimation;
}
private DoubleAnimation CreateSlideOutAnimation()
{
var slideOutAnimation = new DoubleAnimation(
0,
-50,
new Duration(TimeSpan.FromMilliseconds(250)),
FillBehavior.HoldEnd)
{BeginTime = TimeSpan.Zero};
Storyboard.SetTarget(slideOutAnimation, this.PreviousContent);
Storyboard.SetTargetProperty(
slideOutAnimation,
new PropertyPath(
$"{nameof(UIElement.RenderTransform)}.({nameof(TranslateTransform)}.{nameof(TranslateTransform.X)})"));
return slideOutAnimation;
}
private DoubleAnimation CreateFadeInAnimation()
{
var fadeInAnimation = new DoubleAnimation(0, 1, new Duration(TimeSpan.FromMilliseconds(250)), FillBehavior.HoldEnd);
Storyboard.SetTarget(fadeInAnimation, this.NextContent);
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(nameof(UIElement.Opacity)));
return fadeInAnimation;
}
private DoubleAnimation CreateSlideInAnimation()
{
var slideInAnimation = new DoubleAnimation(
-50,
0,
new Duration(TimeSpan.FromMilliseconds(250)),
FillBehavior.HoldEnd);
Storyboard.SetTarget(slideInAnimation, this.NextContent);
Storyboard.SetTargetProperty(
slideInAnimation,
new PropertyPath(
$"{nameof(UIElement.RenderTransform)}.({nameof(TranslateTransform)}.{nameof(TranslateTransform.X)})"));
return slideInAnimation;
}
}