WPF(动画)

在动画中,可以使用移动的元素、颜色变化、变换等制作平滑的变换效果。WPF使动画的制作非常简单。还可以连续改变任意依赖属性的值。不同的动画类可以根据其类型,连续改变不同属性的值。

动画的主要元素如下:

  • 时间轴---定义了值随时间的变化方式。有不同类型的时间轴,可用于改变不同类型的值。所有时间轴的基类都是Timeline。为了连续改变double值,可以使用DoubleAnimation类。Int32Animation类是int值的动画类。PointAnimation类用于连续改变点,ColorAnimation类用于连续改变颜色。
  • 故事板---用于合并动画。Storyboard类派生自基类TimelineGroup,TimelineGroup又派生自基类Timeline。使用DoubleAnimation类,可以连续改变double,使用Storyboard类可以合并所有应连接在一起的动画。
  • 触发器---通过触发器可以启动和停止动画。前面介绍了属性触发器,当属性值变化时,属性触发器就会激活。还可以创建事件触发器,当事件发生时,事件触发器就会激活。 

添加一个当Loaded完成后让椭圆的宽度自动变化的XAML代码: 

<Window x:Class="WpfAppLearn1.TimeLine"
        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:WpfAppLearn1"
        mc:Ignorable="d"
        Title="TimeLine" Height="450" Width="439">
    <Canvas>
        <Ellipse x:Name="ellipse" Height="100" Width="100" Canvas.Left="45" Canvas.Top="180" Fill="Red">
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="Ellipse.Loaded">
                    <EventTrigger.Actions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Width" RepeatBehavior="1x" 
                                                 Duration="0:0:6" AutoReverse="True" 
                                                 FillBehavior="Stop" From="100" To="300" 
                                                 Completed="DoubleAnimation_Completed"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger.Actions>
                </EventTrigger>
            </Ellipse.Triggers>
        </Ellipse>
    </Canvas>
</Window>
Timeline属性说明
AutoReverse指定连续改变的值是否在动画结束后返回初始值(默认为False)
SpeedRatio可以改变动画的移动速度。在这个属性中,可以定义父子元素的相对关系。(默认值为1)
BeginTime

指定从触发器事件激活到动画开始移动之间的时间长度,即延时启动动画。

(默认值为0,值可以为null表示永不播放)

这和SpeedRatio属性有关:若将SpeedRatio设置为2,把BeginTime设置为6秒,动画就在3秒后开始。

AccelerationRatio定义加速度(默认值为0)(在动画中,值不一定是线性变化的。可以指定加减速度)
DecelerationRatio定义减速度(默认值为0)(和加速度的总和不能超过1)
Duration指定动画重复一次的时间长度
RepeatBehavior指定动画的重复次数或者重复时间(默认值为1,表示次数时数字后面加上x,否则就表示时间,Forever表示永久)
FillBehavior

指定动画在到达其有效期末尾后的行为。(默认值为HoldEnd)

  • HoldEnd:在达到活动期的终点后,时间线将保持其进度,直至其父级的活动期和保持期结束为止
  • Stop:如果时间线超出活动期,而其父级在活动期内,则该时间线将停止

这里说明一下FillBehavior的用法:

可以看到XAML中有一个Completed属性,这是一个事件表示动画完成后的响应。

private void DoubleAnimation_Completed(object sender, EventArgs e)
{
    this.ellipse.Width = 10;
}
  • 若是将FillBehavior设置为HoldEnd的话,响应事件后并不会将Width设置为10,因为时间线并没有停止,如果去单独加个按钮设置也一样。只有该为Stop停止时间线后设置才有效。
  • Storyboard其实也继承于Timeline,也可以对其设置上表中的值。当Storyboard中只有一个Timeline时,对Storyboard设置任一个属性后,所有属于Storyboard的Timeline的FillBehavior值都以Storyboard的FillBehavior为准。当有多个时,就以Storyboard的值为准。XAML在将这段解析为代码时,只有一个未设置属性时相当于Storyboard=new(),当有多个时相当于Storyboard.Children.Add().

关键帧动画 

通过使用加减数比和缓动函数,就可以用非线性的方式制作动画。如果需要为动画指定几个值,就可以使用关键帧动画。 

<Window x:Class="WpfAppLearn1.TimeLine"
        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:WpfAppLearn1"
        mc:Ignorable="d"
        Title="TimeLine" Height="350" Width="328">
    <Canvas Background="#FFEAE7E7">
        <Ellipse x:Name="ellipse" Height="20" Width="20">
            <Ellipse.Fill>
                <SolidColorBrush x:Name="ellipseBrush" Color="Red"/>
            </Ellipse.Fill>
            <Ellipse.RenderTransform>
                <TransformGroup>
                    <TranslateTransform x:Name="ellipseMove" X="0" Y="0"/>
                </TransformGroup>
            </Ellipse.RenderTransform>
            <Ellipse.Triggers>
                <EventTrigger RoutedEvent="Ellipse.Loaded">
                    <BeginStoryboard>
                        <Storyboard RepeatBehavior="Forever" AutoReverse="True">
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="X" Storyboard.TargetName="ellipseMove">
                                <LinearDoubleKeyFrame KeyTime="0:0:2" Value="30"/>
                                <DiscreteDoubleKeyFrame KeyTime="0:0:4" Value="80"/>
                                <SplineDoubleKeyFrame KeySpline="0.5,0.0, 0.9,0.0" KeyTime="0:0:10" Value="150"/>
                                <LinearDoubleKeyFrame KeyTime="0:0:20" Value="300"/>
                            </DoubleAnimationUsingKeyFrames>
                            <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Y" Storyboard.TargetName="ellipseMove">
                                <SplineDoubleKeyFrame KeySpline="0.5,0.0 0.9,0.0" KeyTime="0:0:2" Value="50"/>
                                <EasingDoubleKeyFrame KeyTime="0:0:20" Value="300">
                                    <EasingDoubleKeyFrame.EasingFunction>
                                        <BounceEase/>
                                    </EasingDoubleKeyFrame.EasingFunction>
                                </EasingDoubleKeyFrame>
                            </DoubleAnimationUsingKeyFrames>
                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="ellipseBrush" Storyboard.TargetProperty="Color">
                                <ColorAnimationUsingKeyFrames.KeyFrames>
                                    <LinearColorKeyFrame KeyTime="0:0:10" Value="Gold" />
                                    <LinearColorKeyFrame KeyTime="0:0:20" Value="Red" />
                                </ColorAnimationUsingKeyFrames.KeyFrames>
                            </ColorAnimationUsingKeyFrames>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Ellipse.Triggers>
        </Ellipse>
    </Canvas>
</Window>

 关键帧动画将每一个状态制定为一个关键帧,关键帧动画时间线自动连接各个关键帧,并计算过渡状态,完成动画。因此,某种程度上,我们也可以把From/To/By 动画看成是只有两个状态的特殊关键帧动画。

插值算法

在关键帧动画中,我们除了定义关键帧外,还需要定义两个关键帧之间的插值算法,这样系统才能根据关键帧和插值算法生成中间状态。WPF系统内置四种插值算法:

  • 线性:    两个关键帧之间均匀变化

  • 离散:    两个关键帧之间突变(到达时间点的时候硬切换,没有过渡效果)

  • 样条:    使用贝塞尔曲线实现更精确的加速和减速控制

  • 缓动:    使用缓动函数曲线实现弹性变化


可见状态管理器 

Visual State Manager提供了控制动画的另一种方式,控件可以有特定的状态。状态定义了到达该状态时应用于控件的外观。状态切换定义了一种状态变成另一种状态时会发生什么。

<Window x:Class="WpfAppLearn1.VisualState1"
        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:WpfAppLearn1"
        mc:Ignorable="d"
        Title="VisualState" Height="246" Width="379">
    <Canvas HorizontalAlignment="Left" VerticalAlignment="Top">
        <Button Content="Sleeping" Width="75" Canvas.Left="56" Canvas.Top="47" Click="BtnSleep_Click"/>
        <Button Content="Playing" Width="75" Canvas.Left="56" Canvas.Top="97" Click="BtnPlay_Click"/>
        <Button Content="Crying" Width="75" Canvas.Left="56" Canvas.Top="147" Click="BtnCry_Click"/>
        <Ellipse x:Name="ellipse1" Height="100" Stroke="Black" Width="100" Canvas.Left="216" Canvas.Top="54">
            <Ellipse.Fill>
                <SolidColorBrush x:Name="brush1" Color="Aqua"/>
            </Ellipse.Fill>
            <VisualStateManager.VisualStateGroups>
                <VisualStateGroup x:Name="CommonStates">
                    <VisualState x:Name="Sleeping">
                        <Storyboard>
                            <ColorAnimation To="LightBlue" Storyboard.TargetName="brush1" 
                                            Storyboard.TargetProperty="Color" Duration="0:0:5"/>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Playing">
                        <Storyboard>
                            <ColorAnimation To="Gold" Storyboard.TargetName="brush1" 
                                            Storyboard.TargetProperty="Color" Duration="0:0:5"/>
                        </Storyboard>
                    </VisualState>
                    <VisualState x:Name="Crying">
                        <Storyboard>
                            <ColorAnimation To="Red" Storyboard.TargetName="brush1" 
                                            Storyboard.TargetProperty="Color" Duration="0:0:5"/>
                        </Storyboard>
                    </VisualState>
                    <VisualStateGroup.Transitions>
                        <VisualTransition x:Name="ToSleeping" To="Sleeping">
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Width" Duration="0:0:5" To="50"/>
                            </Storyboard>
                        </VisualTransition>
                        <!--若是从Crying切换到Sleeping就不会执行上面的切换状态,只会执行下面的-->
                        <VisualTransition x:Name="CryingToSleeping" From="Crying" To="Sleeping">
                            <Storyboard>
                                <ColorAnimation From="Black" To="White" Storyboard.TargetName="brush1" 
                                                Storyboard.TargetProperty="Color" Duration="0:0:5"/>
                            </Storyboard>
                        </VisualTransition>
                        <!--GeneratedDuration:表示延迟切换-->
                        <VisualTransition To="Playing" GeneratedDuration="0:0:0.5"/>
                    </VisualStateGroup.Transitions>
                </VisualStateGroup>
            </VisualStateManager.VisualStateGroups>
        </Ellipse>
    </Canvas>
</Window>
private void BtnSleep_Click(object sender, RoutedEventArgs e)
{
    VisualStateManager.GoToElementState(ellipse1, "Sleeping", true);
}

private void BtnPlay_Click(object sender, RoutedEventArgs e)
{
    VisualStateManager.GoToElementState(ellipse1, "Playing", true);
}

private void BtnCry_Click(object sender, RoutedEventArgs e)
{
    VisualStateManager.GoToElementState(ellipse1, "Crying", true);
}
  1. VisualStateGroup.Transitions表示状态从From值切换到To值时应执行的效果,没有From表示从任意状态。
  2. VisualState表示状态变更为该值时应执行的效果。 
  3. 点击按钮变换状态时,先执行Transitions中效果再执行VisualState中效果。
  4. 若是从Crying切换到Sleeping就不会执行ToSleeping切换状态,只会执行CryingToSleeping。

posted @ 2022-04-12 22:46  Bridgebug  阅读(202)  评论(0编辑  收藏  举报