WPF中文网

关键帧动画

在DoubleAnimationBase抽象基类下面继承了3个子类,分别是DoubleAnimation、DoubleAnimationUsingKeyFrames和DoubleAnimationUsingPath,其中DoubleAnimationUsingKeyFrames被称为关键帧动画,DoubleAnimationUsingPath被称为路径动画,而DoubleAnimation动画我们已经在上一节学过,这里就不在赘述。在本节课程,我们以DoubleAnimationUsingKeyFrames关键帧动画为例,讲解什么是关键帧动画,以及怎么使用它。

一、什么是关键帧动画

在WPF中所有以KeyFrames结尾的动画都叫关键帧动画。它其实与上一节的动画类似,也是对某个目标属性进行动画处理。DoubleAnimation拥有Form/To,表示从一个值过渡到另一个值。而关键帧动画使用关键帧对象进行描述,而且一个关键帧动画可以拥有多个关键帧对象。

什么是关键帧对象?其实就是一个KeyFrames属性集合中的元素。所以我们需要看一下DoubleAnimationUsingKeyFrames的定义。

二、DoubleAnimationUsingKeyFrames的定义

public class DoubleAnimationUsingKeyFrames : DoubleAnimationBase, IKeyFrameAnimation, IAddChild
{
    public DoubleAnimationUsingKeyFrames();

    public DoubleKeyFrameCollection KeyFrames { get; set; }
    public bool IsAdditive { get; set; }
    public bool IsCumulative { get; set; }

    public DoubleAnimationUsingKeyFrames Clone();
    public DoubleAnimationUsingKeyFrames CloneCurrentValue();
    public bool ShouldSerializeKeyFrames();
    protected virtual void AddChild(object child);
    protected virtual void AddText(string childText);
    protected override void CloneCore(Freezable sourceFreezable);
    protected override void CloneCurrentValueCore(Freezable sourceFreezable);
    protected override Freezable CreateInstanceCore();
    protected override bool FreezeCore(bool isChecking);
    protected override void GetAsFrozenCore(Freezable source);
    protected override void GetCurrentValueAsFrozenCore(Freezable source);
    protected sealed override double GetCurrentValueCore(double defaultOriginValue, double defaultDestinationValue, AnimationClock animationClock);
    protected sealed override Duration GetNaturalDurationCore(Clock clock);
    protected override void OnChanged();

}

在上面的代码中,KeyFrames属性表示DoubleAnimationUsingKeyFrames的关键帧对象集合,这个集合中的元素类型为DoubleKeyFrame,但是DoubleKeyFrame其实是一个抽象类哦,所以真正在使用这个集合时,其中的元素都是DoubleKeyFrame的子类,比如LinearDoubleKeyFrame(线性内插关键帧)。

DoubleKeyFrame有哪些子类?

//离散:两个关键帧之间突变(到达时间点的时候硬切换,没有过渡效果)
System.Windows.Media.Animation.DiscreteDoubleKeyFrame

//缓动:使用缓动函数曲线实现弹性变化
System.Windows.Media.Animation.EasingDoubleKeyFrame

//线性:两个关键帧之间均匀变化
System.Windows.Media.Animation.LinearDoubleKeyFrame

//样条:使用贝塞尔曲线实现更精确的加速和减速控制
System.Windows.Media.Animation.SplineDoubleKeyFrame

结论:一个关键帧对象表示一个动画片段,而一个关键帧动画包含多个关键帧对象。所以,我们可以利用关键帧动画设计出更复杂的动画效果。下面我们以一个实例来说明关键帧的用法。

三、关键帧动画实例

这次我们在XAML前端代码中演示关键帧动画的用法。首先在窗体资源中定义一个故事板。

<Storyboard x:Key="KeyFrameStoryboard">
    <PointAnimationUsingKeyFrames
        Storyboard.TargetName="ellipse"
        Storyboard.TargetProperty="Fill.GradientOrigin"
        AutoReverse="True" 
        RepeatBehavior="Forever">
        <LinearPointKeyFrame Value="0.25,0.25" KeyTime="0:0:0"/>
        <LinearPointKeyFrame Value="0.75,0.35" KeyTime="0:0:1"/>
        <LinearPointKeyFrame Value="0.25,0.75" KeyTime="0:0:2"/>
        <LinearPointKeyFrame Value="0.85,0.85" KeyTime="0:0:3"/>
    </PointAnimationUsingKeyFrames>
</Storyboard>

这个Storyboard 中,我们实例化了一个PointAnimationUsingKeyFrames ,也就是点关键帧动画。这里有3个地方需要注意,第一点,Storyboard.TargetName指向了目标对象;第二点,Storyboard.TargetProperty指向了目标对象的属性;第三点,我们在PointAnimationUsingKeyFrames 的KeyFrames 属性中实例化了4个LinearPointKeyFrame 关键帧对象,这4个对象的Value值首尾相接实现动画处理,而后面的KeyTime,表示时间段上的连续。从这个Value的变化,我们可以得出结论,关键帧可以提供多个动画值输出给目标对象的属性,而一般的动画只有单一个值的变化。

怎么使用这个故事板?

<Ellipse x:Name="ellipse" 
         Canvas.Left="-200"
         Canvas.Top="50"
         Width="200" 
         Height="200">
    <Ellipse.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard Storyboard="{StaticResource KeyFrameStoryboard}"/>
        </EventTrigger>
    </Ellipse.Triggers>
    <Ellipse.Fill>
        <RadialGradientBrush GradientOrigin="0.25,0.25" 
                             RadiusX="0.75" 
                             RadiusY="0.75">
            <RadialGradientBrush.GradientStops>
                <GradientStop Color="White" Offset="0" />
                <GradientStop Color="LightCoral" Offset="0.65" />
                <GradientStop Color="Gray" Offset="0.8" />
            </RadialGradientBrush.GradientStops>
        </RadialGradientBrush>                
    </Ellipse.Fill>
</Ellipse>

我们在椭圆的的Loaded事件中,利用事件触发器EventTrigger去触发这个KeyFrameStoryboard。于是,椭圆的画刷的GradientOrigin属性便被这个动画一直修改。椭圆中间的白点将随着动画输出的坐标点而运动。

下面是直接在Canvas的触发器中定义并使用关键帧动画的XAML代码。

<Canvas.Triggers>
    <EventTrigger RoutedEvent="Canvas.Loaded">
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimationUsingKeyFrames 
                    Storyboard.TargetName="ellipse"
                    Storyboard.TargetProperty="(Canvas.Left)">
                    <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.5"/>
                    <EasingDoubleKeyFrame Value="150" KeyTime="0:0:2">
                        <EasingDoubleKeyFrame.EasingFunction>
                            <SineEase EasingMode="EaseInOut"/>
                        </EasingDoubleKeyFrame.EasingFunction>
                    </EasingDoubleKeyFrame>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
    <EventTrigger RoutedEvent="Canvas.MouseLeftButtonUp">
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ellipse"
                                           Storyboard.TargetProperty="(Canvas.Left)">
                    <LinearDoubleKeyFrame Value="700" KeyTime="0:0:2"/>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
    <EventTrigger RoutedEvent="Canvas.MouseRightButtonUp">
        <BeginStoryboard>
            <Storyboard>
                <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ellipse"
                                           Storyboard.TargetProperty="(Canvas.Left)">
                    <EasingDoubleKeyFrame Value="150" KeyTime="0:0:1">
                        <EasingDoubleKeyFrame.EasingFunction>
                            <CircleEase EasingMode="EaseOut"/>
                        </EasingDoubleKeyFrame.EasingFunction>
                    </EasingDoubleKeyFrame>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </BeginStoryboard>
    </EventTrigger>
</Canvas.Triggers>

在Canvas的事件触发器中,我们一共定义了3种情况,分别是Canvas.Loaded、Canvas.MouseLeftButtonUp和Canvas.MouseRightButtonUp,每一种事件都会触发一个关键帧动画。通过DoubleAnimationUsingKeyFrames关键帧动画去控制ellipse对象相对于Canvas的左边距。

在Canvas.Loaded事件中,我们将椭圆从左到右以动画的形式显示在窗体的中间;

在Canvas.MouseLeftButtonUp事件中,我们让椭圆从中间位置往右运动,直至消失;

在Canvas.MouseRightButtonUp事件中,我们又让椭圆从右边回到窗体的中间位置。

最终形成的源代码如下所示:

<Window x:Class="HelloWorld.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:HelloWorld" 
        xmlns:controls="clr-namespace:HelloWorld.Controls"
        xmlns:helper="clr-namespace:HelloWorld.MVVM"
        mc:Ignorable="d" 
        Title="WPF中文网 - 动画 - www.wpfsoft.com" Height="350" Width="500">    
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <Storyboard x:Key="KeyFrameStoryboard">
            <PointAnimationUsingKeyFrames
                Storyboard.TargetName="ellipse"
                Storyboard.TargetProperty="Fill.GradientOrigin"
                AutoReverse="True" 
                RepeatBehavior="Forever">
                <LinearPointKeyFrame Value="0.25,0.25" KeyTime="0:0:0"/>
                <LinearPointKeyFrame Value="0.75,0.35" KeyTime="0:0:1"/>
                <LinearPointKeyFrame Value="0.25,0.75" KeyTime="0:0:2"/>
                <LinearPointKeyFrame Value="0.85,0.85" KeyTime="0:0:3"/>
            </PointAnimationUsingKeyFrames>
        </Storyboard>
        <Storyboard x:Key="WidthStoryboard" TargetProperty="Width">
            <DoubleAnimation 
                         From="200" 
                         To="300" 
                         Duration="0:0:1.5"
                         AutoReverse="True" 
                         RepeatBehavior="Forever">
            </DoubleAnimation>
        </Storyboard>
        <Storyboard x:Key="HeightStoryboard" TargetProperty="Height">
            <DoubleAnimation 
                         From="200" 
                         To="300" 
                         Duration="0:0:1.5"
                         AutoReverse="True" 
                         RepeatBehavior="Forever">
            </DoubleAnimation>
        </Storyboard>
    </Window.Resources>
    <Canvas x:Name="canvas" Background="Transparent">
        <Canvas.Triggers>
            <EventTrigger RoutedEvent="Canvas.Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimationUsingKeyFrames 
                            Storyboard.TargetName="ellipse"
                            Storyboard.TargetProperty="(Canvas.Left)">
                            <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.5"/>
                            <EasingDoubleKeyFrame Value="150" KeyTime="0:0:2">
                                <EasingDoubleKeyFrame.EasingFunction>
                                    <SineEase EasingMode="EaseInOut"/>
                                </EasingDoubleKeyFrame.EasingFunction>
                            </EasingDoubleKeyFrame>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
            <EventTrigger RoutedEvent="Canvas.MouseLeftButtonUp">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ellipse"
                                                   Storyboard.TargetProperty="(Canvas.Left)">
                            <LinearDoubleKeyFrame Value="700" KeyTime="0:0:2"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
            <EventTrigger RoutedEvent="Canvas.MouseRightButtonUp">
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="ellipse"
                                                   Storyboard.TargetProperty="(Canvas.Left)">
                            <EasingDoubleKeyFrame Value="150" KeyTime="0:0:1">
                                <EasingDoubleKeyFrame.EasingFunction>
                                    <CircleEase EasingMode="EaseOut"/>
                                </EasingDoubleKeyFrame.EasingFunction>
                            </EasingDoubleKeyFrame>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Canvas.Triggers>
        <Ellipse x:Name="ellipse" 
                 Canvas.Left="-200"
                 Canvas.Top="50"
                 Width="200" 
                 Height="200">
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="Loaded">
                    <BeginStoryboard Storyboard="{StaticResource KeyFrameStoryboard}"/>
                </EventTrigger>
            </Ellipse.Triggers>
            <Ellipse.Fill>
                <RadialGradientBrush GradientOrigin="0.25,0.25" 
                                     RadiusX="0.75" 
                                     RadiusY="0.75">
                    <RadialGradientBrush.GradientStops>
                        <GradientStop Color="White" Offset="0" />
                        <GradientStop Color="LightCoral" Offset="0.65" />
                        <GradientStop Color="Gray" Offset="0.8" />
                    </RadialGradientBrush.GradientStops>
                </RadialGradientBrush>                
            </Ellipse.Fill>
        </Ellipse>
    </Canvas>
</Window>

这是在前端XAML代码中使用关键帧中的情况,下一节,我们演示在C#后端使用关键帧。

当前课程源码下载:(注明:本站所有源代码请按标题搜索)

文件名:102-《关键帧动画》-源代码
链接:https://pan.baidu.com/s/1yu-q4tUtl0poLVgmcMfgBA
提取码:wpff

——重庆教主 2023年11月10日

copyright @重庆教主 WPF中文网 联系站长:(QQ)23611316 (微信)movieclip (QQ群).NET小白课堂:864486030 | 本文由WPF中文网原创发布,谢绝转载 渝ICP备2023009518号-1