[英]WPF Frame animation to slide in/out pages is not working properly with data triggers
使用我的應用程序,我嘗試在 WPF 中創建一個“單窗口”應用程序。 因此,框架的內容(如下所示)是通過數據綁定設置的。 為了動畫這個過程,我觸發了 ViewModel 之外的AnimateFrameOutToLeft
- 和AnimateFrameInToLeft
- 屬性。 這應該會產生新頁面“滑入”或“滑出”的效果。 更改框架內容的過程步驟(在 ViewModel 中):
AnimateFrameOutToLeft = true; AnimateFrameOutToLeft = false;
AnimateFrameInToLeft = true; AnimateFrameInToLeft = false;
按此順序(關於數據觸發器),“滑動”頁面的 animation 未顯示。 框架的不透明度為 0。更改順序后,“滑出”頁面的 animation 未顯示。 為什么呢? 我該如何解決這個問題?
AnimateFrameOutToLeft
第二次被觸發時,它不顯示 animationShellView.xaml
<Frame Margin="0" Background="White" Content="{Binding FrameContent, Mode=OneWay}" Focusable="False">
<Frame.Style>
<Style TargetType="{x:Type Frame}">
<Style.Triggers>
<DataTrigger Binding="{Binding AnimateFrameInToLeft}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Margin" From="50,0,0,0" To="0" FillBehavior="HoldEnd"/>
<DoubleAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Opacity" From="0" To="1" FillBehavior="HoldEnd"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
<DataTrigger Binding="{Binding AnimateFrameOutToLeft}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Margin" From="0" To="0,0,50,0" FillBehavior="HoldEnd"/>
<DoubleAnimation Duration="0:0:0.25" Storyboard.TargetProperty="Opacity" From="1" To="0" FillBehavior="HoldEnd"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Frame.Style>
...
</Frame>
ShellView.xaml
<Window ...>
<Window.InputBindings>
<KeyBinding Key="Esc" Command="{Binding KeyPressedCommand}" CommandParameter="Esc"/>
<!-- this binding works -->
</Window.InputBindings>
<Grid>
<Frame Margin="0" Background="White" Content="{Binding FrameContent, Mode=OneWay}" Focusable="False">
<Frame.ContentTemplate>
<ItemContainerTemplate>
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.AnimateFrameToLeft, RelativeSource={RelativeSource AncestorType={x:Type local:ShellView}}}" Value="True">
<!-- this binding does not work -->
...
AnimateFrameToLeft-Property 定義在與上述命令相同的 class 中
由於FillBehavior.HoldEnd
,以前的 animation 正在阻止以下 animation (或動畫屬性)。
另一個錯誤是您操作Frame.Margin
屬性的方式。
請注意,如果您想向左滑出,您的ThicknessAnimation
應該將Margin
從0
設置為-50,0,0,0
(而不是0,0,50,0
)。
這是因為正的FrameworkElement.Margin
只影響周圍的元素,而不影響實際的元素。 這意味着因為Frame
右側沒有相鄰元素,所以您看不到任何效果(否則右側相鄰元素將被向右推 50 像素)。
要影響Frame
,您必須使用負Margin
來“拖動”它。
推薦的方法是為TranslateTransform
設置動畫而不是邊距的厚度。
另請注意,為Frame
本身設置動畫可能不是最佳解決方案。 如果您在Frame
上顯示導航控件,則看到所有內容都進出會看起來很奇怪。 您應該直接為Frame.Content
設置動畫。
DataTrigger
基於屬性的 state。 它由 state 更改觸發並執行DataTrigger.EnterActions
。 一旦 state 回落到原來的 state,就會執行DataTrigger.ExitActions
。 入口 animation(使用FillBehavior.HoldEnd
)持有的“屬性值鎖”不會影響出口 animation。
您應該利用 DataTrigger 的自動 state 跟蹤,並將第二個滑入式DataTrigger
移動到DataTrigger.ExitActions
集合。
在以下示例中, Frame.ContentTemplate
用於為內容設置動畫,而不是Frame
本身。 此示例使用DoubleAnimation
來為框架的ContentControl.RenderTransform
屬性(設置為TranslateTransform
)設置動畫,而不是ThicknessAnimation
:
觸發 animation
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;
}
Animation定義
<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>
屬性Frame.Margin
和Frame.Opacity
仍被先前的 animation 阻止,由於FillBehavior.HoldEnd
設置仍在執行中。
在執行時間軸中的下一個之前,您必須停止之前的 animation。
為開始的BeginStoryboard
命名,例如SlideOutStoryboard
。 然后將針對前StopStoryboard
的BeginStoryboard
添加到滑入式觸發器的EnterActions
集合中:
<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>
這是一個擴展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;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.