Предыдущая анимация блокирует следующую анимацию (или анимированные свойства) из-за 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
), не влияет на анимацию выхода. Вы должны использовать автоматическое отслеживание состояния c с помощью DataTrigger
и переместить вторую скользящую анимацию в коллекцию DataTrigger.ExitActions
.
В следующем примере Frame.ContentTemplate
используется для анимации содержание, а не сам Frame
. Вместо ThicknessAnimation
в этом примере используется DoubleAnimation
для анимации свойства ContentControl.RenderTransform
кадра (для которого установлено значение TranslateTransform
):
Запуск анимации
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
. Затем добавьте 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>
Version 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;
}
}