AnimationSet

AnimationSet 类型表示动画计划,通过 XAML 代码有效地表示 AnimationBuilder 实例。 它可以包含任意数量的动画或活动,公开用于启动和停止动画的方法,以及动画启动或完成时要通知的事件。 跟 AnimationBuilder 一样,AnimationSet 实例也可以共享(例如在 ResourceDictionary 中),然后用于在多个 UI 元素上启动动画计划。 还可以通过 Explicit.Animations 附加属性直接附加到父 UI 元素。

平台 API: AnimationSetAnimationBuilderExplicitITimelineIActivityAnimationScopeAnimationStartedTriggerBehaviorAnimationCompletedTriggerBehaviorStartAnimationActionStopAnimationAction

工作原理

每个集可以包含任意数量的动画范围和单个节点,可以是动画,也可以是“活动”:

  • 动画类型是 XAML 中由 AnimationBuilder 类公开的各种 API 的映射。 它们既可以作为随时可用的“默认”动画,也可以作为可完全配置的“自定义”动画使用。 除了仅定义起始值和最终值之外,每个动画类型还支持使用关键帧。
  • 另一方面,活动是将动画计划与各种自定义逻辑交织在一起的方法,例如触发其他动画或运行任意代码(例如,在动画运行时更新视觉状态)。

这两种类型的动画节点可实现多个接口(例如 ITimelineIActivity),这使得此系统对用户以及对他们自己的方案而言,有极高的可自定义性和可扩展性。

下面介绍了如何在 XAML 中声明简单的动画。 在这种情况下,我们将使用 x:Name,以便可以在代码隐藏中引用它,从而在单击按钮时启动它。 动画还直接附加到 Button,因此可以通过调用 Start() 方法直接启动它,而无需指定要进行动画处理的目标元素。

<!--A simple animation using default animation types-->
<Button Content="Click me!">
    <animations:Explicit.Animations>
        <animations:AnimationSet x:Name="FadeInAnimation">
            <animations:OpacityAnimation From="0" To="1"/>
            <animations:TranslationAnimation From="-20,0,0" To="0"/>
        </animations:AnimationSet>
    </animations:Explicit.Animations>
</Button>

默认情况下,动画面向合成层,因为它可提供最佳性能。 不过,也可以显式面向 XAML 层,这样可支持对在 Button 中用于显示某些文本的画笔颜色进行动画处理等操作。 下面的示例展示了我们将此功能与显式关键帧结合使用的情形,以更精细地控制要运行的动画:

<!--An animation set using a scope and explicit keyframes-->
<Button Content="Click me!" Foreground="White">
    <animations:Explicit.Animations>
        <animations:AnimationSet x:Name="FadeInAnimation">
            <animations:AnimationScope Duration="0:0:1" EasingType="Sine">
                <animations:OpacityAnimation From="0" To="1"/>
                <animations:ColorAnimation Target="(Button.Foreground).(SolidColorBrush.Color)" Layer="Xaml">
                    <animations:ColorKeyFrame Key="0.0" Value="White"/>
                    <animations:ColorKeyFrame Key="0.5" Value="Orange"/>
                    <animations:ColorKeyFrame Key="0.8" Value="Green" EasingType="Linear"/>
                    <animations:ColorKeyFrame Key="1.0" Value="White" EasingType="Cubic" EasingMode="EaseOut"/>
                </animations:ColorAnimation>
            </animations:AnimationScope>
        </animations:AnimationSet>
    </animations:Explicit.Animations>
</Button>

在面向合成层的动画中使用关键帧(在 C# 和 XAML 中声明时)时,关键帧也可以使用表达式动画。 这提供对动画值的额外控制,并允许使用者创建可适应目标元素当前状态的动态动画。 以下是示例:

<!--Keyframes can use expressions as well (on the Composition layer)-->
<Button Content="Click me!">
    <animations:Explicit.Animations>
        <animations:AnimationSet x:Name="RotationAnimation">
            <animations:RotationInDegreesAnimation>
                <animations:ScalarKeyFrame Key="0.0" Value="0"/>
                <animations:ScalarKeyFrame Key="1.0" Expression="Lerp(-180, 180, this.Target.Opacity)"/>
            </animations:RotationInDegreesAnimation>
        </animations:AnimationSet>
    </animations:Explicit.Animations>
</Button>

顺序模式

AnimationSet 类型的另一个功能是 IsSequential 属性,该属性可用于配置动画中顶级元素(动画、活动和范围)的处理方式。

当此属性被设置为 true 时,将按顺序执行每个顶级节点,并且仅在上一个节点完成(并且动画尚未取消)时移动到下一个节点。 这可以与各种 IActivity 对象结合使用,以创建自定义动画计划,这些计划将其他 UI 元素上运行的多个动画组合在一起,而所有同步仍完全通过 XAML 完成。 在与 AnimationScope 结合使用以便在创建和修改动画时更轻松地解析动画中事件的时间线时,它也很有帮助。

下面的示例展示了动画的顺序模式以及在同一计划中组合动画和活动的能力,以及如何使用可用的 API 来组合和交错不同的动画(即使在不同的 UI 元素上):

<!--This set first runs a scope with three animations and waits for its completion.
    Then, an activity is used to trigger another animation on its attached parent.
    When that completes as well, the last animation in this set will be executed.-->
<Button Content="Click me!" Foreground="White">
    <animations:Explicit.Animations>
        <animations:AnimationSet x:Name="SequentialAnimation" IsSequential="True">
            <animations:AnimationScope>
                <animations:ScaleAnimation From="1" To="1.2"/>
                <animations:TranslationAnimation From="-20,0,0" To="0"/>
                <animations:OpacityAnimation From="0" To="1"/>
            </animations:AnimationScope>
            <animations:StartAnimationActivity Animation="{x:Bind AnotherAnimation}"/>
            <animations:ScaleAnimation To="1"/>
        </animations:AnimationSet>
    </animations:Explicit.Animations>
</Button>

<!--This rectangle will wiggle left and right when the activity is reached in the
    sequential animation schedule for the button above. When this animation set
    completes, the one that invoked it will resume playing normally.-->
<Rectangle Height="80" Width="120" Fill="Green">
    <animations:Explicit.Animations>
        <animations:AnimationSet x:Name="AnotherAnimation">
            <animations:TranslationAnimation Duration="0:0:1" From="0" To="0">
                <animations:Vector3KeyFrame Key="0.3" Value="-20,0,0"/>
                <animations:Vector3KeyFrame Key="0.6" Value="20,0,0"/>
            </animations:TranslationAnimation>
        </animations:AnimationSet>
    </animations:Explicit.Animations>
</Rectangle>

与取消相关的相同功能同样适用于 AnimationSet:UI 元素上的每个单独调用都会在内部获取其自己的取消令牌,该令牌可用于通过调用 Stop() 方法或其重载之一来停止正在运行的动画。 同一令牌也会被转发到计划中的所有已调用活动,因此停止动画集也会自动停止所有链接的动画和活动。

行为

如果还引用 Behaviors 包,则还可以使用行为和操作来更好地支持新 API,例如在引发给定事件时自动触发动画(完全来自 XAML)。 此包中引入了四种可与动画 API 互操作的主要类型:

  • AnimationStartedTriggerBehaviorAnimationCompletedTriggerBehavior:这些是自定义触发器,可用于在 AnimationSet 启动或完成时执行 IAction-s。 可以从“行为”包中使用所有内置 IAction 对象,以及使用自定义对象。
  • StartAnimationAction:一个 IAction 对象,可在行为中使用,以通过附加的 UI 元素或者要进行动画处理的显式目标来轻松启动目标动画。
  • StopAnimationAction:一个 IAction 对象,可在行为中使用,以通过附加的 UI 元素或者要进行动画处理的显式目标来轻松停止目标动画。

下面是一个示例,展示如何将这些新 API 一起使用:

<Button>
    <!--Use StartAnimationAction to trigger the animation on click-->
    <Interactivity:Interaction.Behaviors>
        <Interactions:EventTriggerBehavior EventName="Click">
            <behaviors:StartAnimationAction Animation="{x:Bind ScaleAnimation}" />
        </Interactions:EventTriggerBehavior>
    </Interactivity:Interaction.Behaviors>
    <animations:Explicit.Animations>
        <animations:AnimationSet x:Name="ScaleAnimation">
            <animations:ScaleAnimation From="1" To="1.2"/>

            <!--Use AnimationEndBehavior to invoke a command when the animation ends-->
            <Interactivity:Interaction.Behaviors>
                <behaviors:AnimationEndBehavior>
                    <Interactions:InvokeCommandAction Command="{x:Bind ViewModel.MyCommand}"/>
                </behaviors:AnimationEndBehavior>
            </Interactivity:Interaction.Behaviors>
        </animations:AnimationSet>
    </animations:Explicit.Animations>
</Button>

这样一来,便无需命名目标 UI 元素,无需在代码隐藏中注册事件处理程序,以及在许多情况下,如果其他动画完全不需要引用 AnimationSet 实例,甚至根本无需命名该实例。 生成的代码全部在 XAML 中,根本不需要代码隐藏!

<!--  Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information.  -->
<Page x:Class="BehaviorsExperiment.Samples.InvokeActionsActivitySample"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:ani="using:CommunityToolkit.WinUI.Animations"
      xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
      xmlns:local="using:BehaviorsExperiment.Samples"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Button Width="140"
            Height="140"
            HorizontalAlignment="Center"
            VerticalAlignment="Center">
        <ani:Explicit.Animations>
            <ani:AnimationSet x:Name="MoveAnimation"
                              IsSequential="True">
                <ani:StartAnimationActivity Animation="{Binding ElementName=FadeOutAnimation}" />
                <ani:InvokeActionsActivity>
                    <interactivity:ChangePropertyAction PropertyName="Foreground"
                                                        TargetObject="{Binding ElementName=MyText}"
                                                        Value="Purple" />
                    <!--<mediaactions:PlaySoundAction Source="Assets/Llama.mp3"/>-->
                </ani:InvokeActionsActivity>
                <ani:StartAnimationActivity Animation="{Binding ElementName=FadeInAnimation}"
                                            Delay="0:0:2" />
            </ani:AnimationSet>
        </ani:Explicit.Animations>

        <TextBlock x:Name="MyText"
                   Text="🦙 Text">
            <ani:Explicit.Animations>
                <ani:AnimationSet x:Name="FadeOutAnimation">
                    <ani:OpacityAnimation Delay="0"
                                          EasingMode="EaseOut"
                                          EasingType="Linear"
                                          From="1"
                                          To="0"
                                          Duration="0:0:1" />
                </ani:AnimationSet>
                <ani:AnimationSet x:Name="FadeInAnimation">
                    <ani:OpacityAnimation Delay="0"
                                          EasingMode="EaseOut"
                                          EasingType="Linear"
                                          From="0"
                                          To="1"
                                          Duration="0:0:1" />
                </ani:AnimationSet>
            </ani:Explicit.Animations>
        </TextBlock>

        <interactivity:Interaction.Behaviors>
            <interactivity:EventTriggerBehavior EventName="Click">
                <interactivity:ChangePropertyAction PropertyName="Foreground"
                                                    TargetObject="{Binding ElementName=MyText}"
                                                    Value="White" />
                <behaviors:StartAnimationAction Animation="{Binding ElementName=MoveAnimation}" />
            </interactivity:EventTriggerBehavior>
        </interactivity:Interaction.Behaviors>
    </Button>
</Page>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.WinUI.Animations;

namespace BehaviorsExperiment.Samples;

[ToolkitSample(id: nameof(InvokeActionsActivitySample), "InvokeActionsActivity", description: $"A sample for showing how to create and use a {nameof(StartAnimationActivity)} behavior.")]
public sealed partial class InvokeActionsActivitySample : Page
{
    public InvokeActionsActivitySample()
    {
        this.InitializeComponent();
    }
}
<!--  Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information.  -->
<Page x:Class="BehaviorsExperiment.Samples.StartAnimationActivitySample"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:ani="using:CommunityToolkit.WinUI.Animations"
      xmlns:behaviors="using:CommunityToolkit.WinUI.Behaviors"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
      xmlns:local="using:BehaviorsExperiment.Samples"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Button Width="140"
            Height="140"
            HorizontalAlignment="Center"
            VerticalAlignment="Center">
        <ani:Explicit.Animations>
            <ani:AnimationSet x:Name="MoveAnimation"
                              IsSequential="True">
                <ani:TranslationAnimation From="0,0,0"
                                          To="0,32,0"
                                          Duration="0:0:3" />
                <ani:StartAnimationActivity Animation="{Binding ElementName=FadeOutAnimation}"
                                            Delay="0:0:3" />
                <ani:StartAnimationActivity Animation="{Binding ElementName=FadeInAnimation}"
                                            Delay="0:0:3" />
                <ani:TranslationAnimation From="0,32,0"
                                          To="0,0,0"
                                          Duration="0:0:1" />
            </ani:AnimationSet>
        </ani:Explicit.Animations>

        <Image Width="68"
               Height="68"
               Source="ms-appx:///Assets/ToolkitIcon.png">
            <ani:Explicit.Animations>
                <ani:AnimationSet x:Name="FadeOutAnimation">
                    <ani:OpacityAnimation Delay="0"
                                          EasingMode="EaseOut"
                                          EasingType="Linear"
                                          From="1"
                                          To="0"
                                          Duration="0:0:1" />
                </ani:AnimationSet>
                <ani:AnimationSet x:Name="FadeInAnimation">
                    <ani:OpacityAnimation Delay="0"
                                          EasingMode="EaseOut"
                                          EasingType="Linear"
                                          From="0"
                                          To="1"
                                          Duration="0:0:1" />
                </ani:AnimationSet>
            </ani:Explicit.Animations>
        </Image>

        <interactivity:Interaction.Behaviors>
            <interactivity:EventTriggerBehavior EventName="Click">
                <behaviors:StartAnimationAction Animation="{Binding ElementName=MoveAnimation}" />
            </interactivity:EventTriggerBehavior>
        </interactivity:Interaction.Behaviors>
    </Button>
</Page>
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.WinUI.Animations;

namespace BehaviorsExperiment.Samples;

[ToolkitSample(id: nameof(StartAnimationActivitySample), "StartAnimationActivity", description: $"A sample for showing how to create and use a {nameof(InvokeActionsActivity)} behavior.")]
public sealed partial class StartAnimationActivitySample : Page
{
    public StartAnimationActivitySample()
    {
        this.InitializeComponent();
    }
}

特效动画

最后,AnimationSet 类还可以直接对 Composition/Win2D 效果进行动画处理。 若要获取此功能的访问权限,还需要引用 CommunityToolkit.WinUI.Media。 此包包含一些特殊的动画类型,可以将这些类型插入 AnimationSet 实例,并用于在自定义效果图中对单个效果进行动画处理。 然后,可以从 PipelineBrush 或通过 PipelineVisualFactory 类型附加到 UI 元素的内联图中使用此功能。 所有这些效果动画都由同一 AnimationBuilder 类型在后台提供支持,有助于在图形中的特效上创建复杂的动画。

以下示例展示了如何将新 PipelineVisualFactory 类型与这些效果动画组合:

<Button>
    <!--Behavior to trigger the animation on click-->
    <Interactivity:Interaction.Behaviors>
        <Interactions:EventTriggerBehavior EventName="Click">
            <behaviors:StartAnimationAction Animation="{x:Bind MyAnimationSet}" />
        </Interactions:EventTriggerBehavior>
    </Interactivity:Interaction.Behaviors>

    <!--VisualFactory to create and attach a custom Win2D/Composition pipeline-->
    <media:UIElementExtensions.VisualFactory>
        <media:PipelineVisualFactory Source="{media:BackdropSource}">
            <media:BlurEffect x:Name="ImageBlurEffect" Amount="32" IsAnimatable="True"/>
            <media:SaturationEffect x:Name="ImageSaturationEffect" Value="0" IsAnimatable="True"/>
            <media:ExposureEffect x:Name="ImageExposureEffect" Amount="0" IsAnimatable="True"/>
        </media:PipelineVisualFactory>
    </media:UIElementExtensions.VisualFactory>

    <!--AnimationSet mixing UI element animations and effect animations-->
    <animations:Explicit.Animations>
        <animations:AnimationSet x:Name="MyAnimationSet">
            <animations:AnimationScope Duration="0:0:5" EasingMode="EaseOut">
                <animations:ScaleAnimation From="1.1" To="1"/>
                <animations:BlurEffectAnimation From="32" To="0" Target="{x:Bind ImageBlurEffect}"/>
                <animations:SaturationEffectAnimation From="0" To="1" Target="{x:Bind ImageSaturationEffect}"/>
                <animations:ExposureEffectAnimation From="1" To="0" Target="{x:Bind ImageExposureEffect}"/>
            </animations:AnimationScope>
        </animations:AnimationSet>
    </animations:Explicit.Animations>
    
    <!--Button content here...-->
</Button>

在这里,我们将设置创建画笔后要进行动画处理的效果的 IsAnimatable 属性。 此操作是必要的,因为 Win2D/合成效果默认情况下不支持动画,并且在创建合成画笔时需要执行额外的设置才能启用此功能。 默认情况下,管道中的效果不是全部配置为可进行动画处理,这既是为了减少开销,也是因为单个画笔中可以进行动画处理的效果数量存在限制。 用户选择使用这一更高级的功能可确保即使在非常大的管道内也能对效果进行动画处理,而不会因此限制而产生问题。