WPF入门到跪下 第八章 动画-基础

发布时间:2024年01月10日

WPF动画分类

在WPF中,与动画相关的的类型具体如下:

  • 简单线性动画:17个类型名 + Animation
  • 关键帧动画:22个类型名 + AnimationUsingKeyFrames
  • 路径动画:3个类型名 + AnimationUsingPath

尽管看起来很多,但实际上里面由很多是在使用上都是类似的,而且在实际开发过程中正真用到的只是其中的小部分,其中用的最多的是简单线性动画,所以不必慌张。

WPF动画中的关键对象
WPF中实现动画由两个关键的因素,即动画类故事板
动画类需要根据不同的需求、场景来进行选择。
故事板在动画开发中也是十分重要的,故事板相当于动画的舞台,而动画类则相当于动画的演员。舞台上根据时间线在指定的时间节点安排演员上场,组成完整动画。

WPF动画的必要条件
对象必须实现Animatable接口(界面控件基本都实现了)。
关联属性必须是依赖属性。
需要有与属性对应类型的动画类,比如说控件的Width属性,其类型是Double类型,Double类型对应的动画类型为DoubleAnimation。而有些属性是没有对应的动画类的,比如string类型是没有对应的简单线性动画类的,此时如果是关键帧动画,而恰好要进行动画的属性是没有动画类型的,可以使用ObjectAnimationUsingKeyFrames


故事板与简单线性动画

一、故事板

StoryBoard对象用于控制动画的运行,包括开始、停止、暂停、恢复等。

此外,当我们定义了动画实例后,这个动画实例要执行在哪个对象上,也是需要通过StoryBoard的附加属性来处理的。

StoryBoard元素中可以放入多个动画对象。

故事板中的常用属性

SpeedRatio:动画过程的速率,不影响动画时间,只是按照比例加快原有速度。

AccelerationRatioDecelerationRatio:设置动画在过程时间中的加速和减速,有效值为0-1,表示在动画过程时间的百分比时间内进行加速或减速。

AutoReverse:动画完成后是否执行相反的动画。

FillBehavior:动画结束结束后控件的状态。

  • HoldEnd:保持动画结束后的状态。(默认)
  • Stop:回到动画开始前的状态。

RepeatBehavior:动画重复方式,包括三种值:Forever、次数(3x)、时间(0:0:10)

关于故事板的其他操作,放在本章最后。

二、简单线性动画

简单线性动画相关的常用类型共有17个之多,用起来基本上都差不多,因此这里挑几个比较常用的作为例子进行学习。

1、简单线性动画控件的常用属性

Duration:时间,时:分:秒,例:Duration="0:0:1"

From:起始数据,可以不设置,直接从当前控件对象中获取数据。

To:结束数据。

By:从控件对象对应属性的当前值开始增量变化到指定数值,比如原先是50,那么By设定成200的最终值就是250。设置了By属性则不需要在设置FromTo

Storyboard.TargetName:故事板的附加依赖属性,用于指定动画的作用对象。

Storyboard.TargetProperty:故事板的附加依赖属性,用于指定动画的作用属性。

RepeatBehavior:动画重复,可以设置重复的次数、时间或者永远重复。

  • Forever:永远重复。
  • 次数:3x,表示三次。
  • 时间:时:分:秒,设置在动画开始后多长时间内不断重复。
  • StoryBorad中也有RepeatBehavior属性,其对所有的子动画元素生效,优先级低于子动画元素在内联方式的重复设置。

BeginTime:设置动画的延时开始的时间,时:分:秒。

AutoReverse:动画完成后是否自动折返(恢复)。

2、简单线性动画实例

控件宽度变化
Xaml中进行动画设定

<Window ......>
    <Window.Resources>
        <Storyboard x:Key="sb">
            <DoubleAnimation 
                Duration="0:0:1"
                From="50"
                To="300"
                Storyboard.TargetName="bd"
                Storyboard.TargetProperty="Width"/>
        </Storyboard>
    </Window.Resources>
    <DockPanel>
        <Button Content="动画" Click="Button_Click" DockPanel.Dock="Bottom" Name="btn_Begin"/>
        <Border x:Name="bd" Background="Yellow" Height="50" Width="50"/>
    </DockPanel>
</Window>

Click事件函数中开启动画

private void Button_Click(object sender, RoutedEventArgs e)
{
    (Resources["sb"] as Storyboard).Begin();
}

除了在代码中开启动画之外,还可以在XAML中通过事件触发器来开启动画

<Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_Begin">
	    	<BeginStoryboard Storyboard="{StaticResource sb}"/>
  	</EventTrigger>
</Window.Triggers>

控件的移动
控件的移动可以通过控制Margin属性来实现

<Window.Resources>
    <Storyboard x:Key="sb">
        <ThicknessAnimation 
            Duration="0:0:1"
            To="100 0 0 0"
            Storyboard.TargetName="bd"
            Storyboard.TargetProperty="Margin"
            AutoReverse="True"
            RepeatBehavior="Forever"/>
    </Storyboard>
</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_Begin">
        <BeginStoryboard Storyboard="{StaticResource sb}"/>
    </EventTrigger>
</Window.Triggers>
<DockPanel>
    <Button Content="动画" DockPanel.Dock="Bottom" Name="btn_Begin"/>
    <Border x:Name="bd" Background="Yellow" Height="50" Width="50"/>
</DockPanel>

3、动画类型的选用

关于动画类型的选用,根据要进行动画的属性去选用就可以了,比如Width的类型为Double,那么就用DoubleAnimation动画类型;Margin的类型为Thinkness,则使用ThicknessAnimation动画类型。
DoubleAnimationThicknessAnimation动画类型的使用是比较直接的,需要注意的是ColorAnimation类型由于此动画类型多用于BackgroundFill等属性上,而这些属性并不是单纯的插值属性,而是Brush画刷对象,ColorAnimation所对应的应该是Brush画刷对象中的Color属性,因此正确的用法如下:

......
    <ColorAnimation Duration="0:0:0" To="Red" Storyboard.TargetProperty="Color" Storyboard.TargetName="LineStroke"/>
......
<Line ......>
    <Line.Stroke>
        <SolidColorBrush x:Name="LineStroke" Color="Blue"/>
    </Line.Stroke>
</Line>

也可以用如下简化写法

......
    <ColorAnimation Duration="0:0:0" To="Red" Storyboard.TargetProperty="(Line.Stroke).(SolidColorBrush.Color)" Storyboard.TargetName="line"/>
......
<Line ......  x:Name="line" Stroke="Blue"/>

关键帧动画

关键帧动画的关键帧类型是根据不同的动画特性来进行分类的,主要分为线性变化关键帧、离散变化关键帧、样条关键帧、缓冲式关键帧。
此外,在xaml中,关键帧元素是要放到关键帧动画类型元素中的,而关键帧动画类型是根据要进行动画的元素的属性类型来区分的,比如要进行动画变化的是Text属性,Text属性是string类型,所以使用StringAnimationUsingKeyFrames关键帧动画类型。

一、线性变化关键帧

Linear+[类型]+KeyFrame:线性变化关键帧,跟简单线性动画的处理基本一样,差别在于可以在时间线中自由设置每个关键帧之间的时间及变化。

  • KeyTime:从初始值到目标值的过程时间,时:分:秒。
  • Value:属性的目标值。

示例

<Window.Resources>
    <Storyboard x:Key="sb">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="Width">
            <LinearDoubleKeyFrame KeyTime="0:0:2" Value="150"/>
            <LinearDoubleKeyFrame KeyTime="0:0:4" Value="100"/>
            <LinearDoubleKeyFrame KeyTime="0:0:6" Value="150"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
        <BeginStoryboard Storyboard="{StaticResource sb}"/>
    </EventTrigger>
</Window.Triggers>
<Grid>
    <StackPanel>
        <Border Background="Yellow" Width="60" Height="30" Name="border"/>
        <Button Content="开始动画" Name="btn_Start"/>
    </StackPanel>
</Grid>

二、离散关键帧

Discrete+[类型]+KeyFrame:离散变化关键帧,到时间节点立即变化,过程中不发生连续变化。

  • KeyTime:关键帧的插入时间,时:分:秒。
  • Value:插入关键帧后,属性的目标值。

示例

<Window.Resources>
    <Storyboard x:Key="sb">
        <StringAnimationUsingKeyFrames Storyboard.TargetName="txtb_Info" Storyboard.TargetProperty="Text">
            <DiscreteStringKeyFrame KeyTime="0:0:0" Value="H"/>
            <DiscreteStringKeyFrame KeyTime="0:0:0.5" Value="He"/>
            <DiscreteStringKeyFrame KeyTime="0:0:1" Value="Hel"/>
            <DiscreteStringKeyFrame KeyTime="0:0:1.5" Value="Hell"/>
            <DiscreteStringKeyFrame KeyTime="0:0:2" Value="Hello"/>
        </StringAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
        <BeginStoryboard Storyboard="{StaticResource sb}"/>
    </EventTrigger>
</Window.Triggers>
<Grid>
    <StackPanel>
        <TextBlock Name="txtb_Info"/>
        <Button Content="开始动画" Name="btn_Start"/>
    </StackPanel>
</Grid>

三、样条关键帧

Spline+[类型]+KeyFrame:样条关键帧,可以使用样条函数(二次贝塞尔曲线-Path),来调整动画的过程。

  • KeyTime:从初始值到目标值的过程时间,时:分:秒。
  • Value:属性的目标值。
  • KeySpline:设置二次贝塞尔曲线的控制点,分别为第一个控制点和第二个控制点,KeySpline="0.2,0.3 0.8,0.4"。这里的贝塞尔曲线上的坐标系是以过程时间作为x轴分为0-1,起始值到目标值作为y轴分为0-1形成的。(可以打开Blend查看)

示例

<Window.Resources>
    <Storyboard x:Key="sb">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="tt" Storyboard.TargetProperty="X" AutoReverse="True">
            <SplineDoubleKeyFrame KeyTime="0:0:3" Value="375" KeySpline="0.2,0.3 0.3,0.8"/>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
        <BeginStoryboard Storyboard="{StaticResource sb}"/>
    </EventTrigger>
</Window.Triggers>
<Grid>
    <StackPanel>
        <Border Background="Yellow" Width="400" Height="30" Name="border">
            <Ellipse  Width="25" Height="25" Stroke="White" Fill="White" HorizontalAlignment="Left">
                <Ellipse.RenderTransform>
                    <TranslateTransform X="0" x:Name="tt"/>
                </Ellipse.RenderTransform>
            </Ellipse>
        </Border>
        <Button Content="开始动画" Name="btn_Start"/>
    </StackPanel>
</Grid>

四、缓动关键帧

EasingDoubleKeyFrame:缓动关键帧,使用WPF中预置的动画效果。

  • KeyTime:从初始值到目标值的过程时间,时:分:秒。
  • Value:属性的目标值。
  • EasingFunction:应用于该关键帧的缓动函数,EasingFunction并不只是对EasingDoubleKeyFrame有效,对简单线性动画也是支持的。
    • BounceEase:乒乓球的弹跳效果。
    • 不同的缓动函数中还可以通过自己特有的属性进行一些缓动设置,这些有用到再摸索摸索。

示例

<Window.Resources>
    <Storyboard x:Key="sb">
        <DoubleAnimationUsingKeyFrames Storyboard.TargetName="border" Storyboard.TargetProperty="Width">
            <EasingDoubleKeyFrame KeyTime="0:0:2" Value="200">
                <EasingDoubleKeyFrame.EasingFunction>
                    <BounceEase/>
                </EasingDoubleKeyFrame.EasingFunction>
            </EasingDoubleKeyFrame>
        </DoubleAnimationUsingKeyFrames>
    </Storyboard>
</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click">
        <BeginStoryboard Storyboard="{StaticResource sb}"/>
    </EventTrigger>
</Window.Triggers>
<Grid>
    <StackPanel>
        <Border Background="Yellow" Width="50" Height="30" Name="border"/>
        <Button Content="开始动画" Name="btn_Start"/>
    </StackPanel>
</Grid>

路径动画

路径动画可以根据Path微语言数据来设定动画行走轨迹。

路径动画类型只有三种,分别为DoubleAnumationUsingPathPoinAnimationUsingPathMatrixAnimationUsingPath

一、DoubleAnumationUsingPath

DoubleAnumationUsingPath:用于进行单个数值的路径动画实现。

  • Storyboard.TargetNameStoryboard的附加依赖属性,设置实现动画的目标控件。
  • Storyboard.TargetPropertyStoryboard的附加依赖属性,设置实现动画的具体属性。
  • Duration:从初始值到目标值的过程时间。
  • Source:从路径微语言中获取哪个属性。
  • PathGeometry:路径几何对象,通过Figures属性使用微语言来设置动画路径。

示例

<Window.Resources>
    <Storyboard x:Key="pathAnimation">
        <DoubleAnimationUsingPath Storyboard.TargetName="ttMove" 
                                  Storyboard.TargetProperty="X"
                                  Duration="0:0:4"
                                  Source="X">
            <DoubleAnimationUsingPath.PathGeometry>
                <PathGeometry Figures="M0,0 L100,100 a100,100,0,0,0,200,200"/>
            </DoubleAnimationUsingPath.PathGeometry>
        </DoubleAnimationUsingPath>
        <DoubleAnimationUsingPath Storyboard.TargetName="ttMove" 
                                  Storyboard.TargetProperty="Y"
                                  Duration="0:0:4"
                                  Source="Y">
            <DoubleAnimationUsingPath.PathGeometry>
                <PathGeometry Figures="M0,0 L100,100 a100,100,0,0,0,200,200"/>
            </DoubleAnimationUsingPath.PathGeometry>
        </DoubleAnimationUsingPath>
        <DoubleAnimationUsingPath Storyboard.TargetName="rtMove" 
                                  Storyboard.TargetProperty="Angle"
                                  Duration="0:0:4"
                                  Source="Angle">
            <DoubleAnimationUsingPath.PathGeometry>
                <PathGeometry Figures="M0,0 L100,100 a100,100,0,0,0,200,200"/>
            </DoubleAnimationUsingPath.PathGeometry>
        </DoubleAnimationUsingPath>
    </Storyboard>
</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_Move">
        <BeginStoryboard Storyboard="{StaticResource pathAnimation}"/>
    </EventTrigger>
</Window.Triggers>
<Grid>
    <Rectangle Name="e_Move" Fill="Red" Width="30" Height="30" HorizontalAlignment="Left" VerticalAlignment="Top" RenderTransformOrigin="0.5 0.5">
        <Rectangle.RenderTransform>
            <TransformGroup>
                <!--这里注意变形的顺序-->
                <RotateTransform Angle="0" x:Name="rtMove"/>
                <TranslateTransform X="0" Y="0" x:Name="ttMove"/>
            </TransformGroup>
        </Rectangle.RenderTransform>
    </Rectangle>
    <Button Name="btn_Move" Width="60" Height="20" Content="开始动画"/>
</Grid>

二、PointAnimationUsingPath

PointAnimationUsingPath:针对控件的Point类型属性进行动画实现。

  • Storyboard.TargetNameStoryboard的附加依赖属性,设置实现动画的目标控件。
  • Storyboard.TargetPropertyStoryboard的附加依赖属性,设置实现动画的具体属性。
  • Duration:从初始值到目标值的过程时间。
  • PathGeometry:路径几何对象,通过Figures属性使用微语言来设置动画路径。

PointAnimationUsingPath并不需要设置路径的数值来源,会自动获取路径的XY来作为Point的值。

示例

<Window.Resources>
    <Storyboard x:Key="pathAnimation">
        <PointAnimationUsingPath Storyboard.TargetName="ellipse" 
                                  Storyboard.TargetProperty="Center"
                                  Duration="0:0:4">
            <PointAnimationUsingPath.PathGeometry>
                <PathGeometry Figures="M20,20 L100,100 a100,100,0,0,0,200,200"/>
            </PointAnimationUsingPath.PathGeometry>
        </PointAnimationUsingPath>
    </Storyboard>
</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_Move">
        <BeginStoryboard Storyboard="{StaticResource pathAnimation}"/>
    </EventTrigger>
</Window.Triggers>
<Grid>
    <Path Fill="Red">
        <Path.Data>
            <EllipseGeometry Center="20 20" RadiusX="20" RadiusY="20" x:Name="ellipse"/>
        </Path.Data>
    </Path>
    <Button Name="btn_Move" Width="60" Height="20" Content="开始动画"/>
</Grid> 

三、MatrixAnimationUsingPath

MatrixAnimationUsingPath:使用矩阵变换来实现动画。

  • Storyboard.TargetNameStoryboard的附加依赖属性,设置实现动画的目标控件。
  • Storyboard.TargetPropertyStoryboard的附加依赖属性,设置实现动画的具体属性。
  • Duration:从初始值到目标值的过程时间。
  • PathGeometry:路径几何对象,通过Figures属性使用微语言来设置动画路径。

MatrixAnimationUsingPath不需要关心矩阵数据来源,会根据微语言路径自动获取,用起来还是很方便的。

示例

<Window.Resources>
    <Storyboard x:Key="pathAnimation">
        <MatrixAnimationUsingPath Storyboard.TargetName="mt" 
                                  Storyboard.TargetProperty="Matrix"
                                  Duration="0:0:4">
            <MatrixAnimationUsingPath.PathGeometry>
                <PathGeometry Figures="M20,20 L100,100 a100,100,0,0,0,200,200"/>
            </MatrixAnimationUsingPath.PathGeometry>
        </MatrixAnimationUsingPath>
    </Storyboard>
</Window.Resources>
<Window.Triggers>
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_Move">
        <BeginStoryboard Storyboard="{StaticResource pathAnimation}"/>
    </EventTrigger>
</Window.Triggers>
<Grid>
    <Ellipse x:Name="ellipse" Width="30" Height="30" HorizontalAlignment="Left" VerticalAlignment="Top" Fill="Red">
        <Ellipse.RenderTransform>
            <MatrixTransform x:Name="mt"/>
        </Ellipse.RenderTransform>
    </Ellipse>
    <Button Height="20" Width="50" Content="开始动画" Name="btn_Move"/>
</Grid>

动画类型的扩展属性

所有的动画类型中,时常会用到的还有以下两个属性。
IsAddtive:将目标属性的当前值添加到动画的起始值,也就是如果动画类型中设置了From属性,那么每次会将目标属性的当前值加上From设定的值后再进行动画。
IsCumulative:如果动画不断重复,就累积动画值。这里的不断重复指的是在动画类型中设置了RepeatBehavior属性(不能再Storyboard中设置,否则无效)。

生命周期事件与动画控制

一、动画的生命周期事件

Completed:整个故事板中的动画完成时触发。

CurrentGlobalSpeedInvalidated:动画中的速度发生变化时触发,比如从静止到运动,运动到静止。

CurrentStateInvalidated:状态变化,比如动画开始,动画结束。

CurretnTimeInvalidated:时间线的变化时触发,也就是在运动过程中随着时间的行进一直触发。

RemoveRequested:动画正被移除的时候触发

<Storyboard x:Key="sb_event"
            Completed="Storyboard_Completed"
            CurrentGlobalSpeedInvalidated="Storyboard_CurrentGlobalSpeedInvalidated"
            CurrentTimeInvalidated="Storyboard_CurrentTimeInvalidated"
            CurrentStateInvalidated="Storyboard_CurrentStateInvalidated">
    <DoubleAnimation ....../>
</Storyboard>

二、动画的控制

BeginStoryboard:开始一个故事板的动画。

  • Storyboard:指定开始哪个故事板对象。
  • NameBeginStoryborad的对象名称,给后续操作进行关联。

PauseStoryboard:暂停动画。

  • BeginStoryboardName:指定要暂停的是哪个BeginStoryboard对象。

ResumeStoryboard:恢复动画。

  • BeginStoryboardName:指定要暂停的是哪个BeginStoryboard对象。

StopStoryboard:停止动画,让动画恢复原始状态。

  • BeginStoryboardName:指定要暂停的是哪个BeginStoryboard对象。

SeekStoryboard:跳转某一帧,某个时刻

  • BeginStoryboardName:指定要暂停的是哪个BeginStoryboard对象。
  • Offset:跳转到动画时间线上的那个时间点上,Offset="0:0:3"

SetStoryboardSpeedRatio:对动画进行加速或减速,有效值表示对动画速度的倍数,大于1为加速,大于0小于1为减速。

  • BeginStoryboardName:指定要暂停的是哪个BeginStoryboard对象。
  • SpeedRatio:速度倍数。

1、使用EventTrigger进行控制

从上面的各个操作对象也可以看出来,除了BeginStoryboard需要指定故事板和自己的名字外,其他动画操作都是基于BeginStoryboard对象来实行的。

<Window.Triggers>
    <!--开始-->
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_1">
        <BeginStoryboard Storyboard="{StaticResource sb}" Name="bsb"/>
    </EventTrigger>
    <!--暂停-->
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_2">
        <PauseStoryboard BeginStoryboardName="bsb"/>
    </EventTrigger>
    <!--恢复-->
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_3">
        <ResumeStoryboard BeginStoryboardName="bsb"/>
    </EventTrigger>
    <!--停止-->
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_4">
        <StopStoryboard BeginStoryboardName="bsb"/>
    </EventTrigger>
    <!--跳转到某个时刻-->
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_5">
        <SeekStoryboard BeginStoryboardName="bsb" Offset="0:0:3"/>
    </EventTrigger>
    <!--加速-->
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_6">
        <SetStoryboardSpeedRatio BeginStoryboardName="bsb" SpeedRatio="3"/>
    </EventTrigger>
    <!--减速-->
    <EventTrigger RoutedEvent="Button.Click" SourceName="btn_7">
        <SetStoryboardSpeedRatio BeginStoryboardName="bsb" SpeedRatio="0.3"/>
    </EventTrigger>
</Window.Triggers>

2、使用DataTrigger进行控制

除了使用EventTrigger进行动画的操作外,还可以通过DataTriggerDataTrigger.EnterActionsDataTrigger.ExitActions属性来根据绑定数据的变化进行动画的设置。
DataTrigger.EnterActions:用于定义当条件满足时的行为(动作的集合)。
DataTrigger.ExitActions:用于定义当条件不满足时的行为。
这里所说的行为就是一系列动作的集合,而动作指的是任何派生自TriggerAction类的对象,常为BeginStoryboardStopStoryboard等动画操作动作。

示例

<Path Stroke="Transparent" StrokeThickness="8" StrokeDashArray="0.1 3" StrokeDashCap="Round" Opacity="0.8" x:Name="rf_Flow">
     <Path.Style>
         <Style TargetType="Path">
             <Style.Triggers>
                 <DataTrigger Binding="{Binding RF_OnOffStatus}" Value="True">
                     <DataTrigger.EnterActions>
                         <BeginStoryboard Name="flowBegin">
                             <Storyboard RepeatBehavior="Forever" FillBehavior="Stop">
                                 <ColorAnimation Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)" To="WhiteSmoke" Duration="0:0:0.2"/>
                                 <DoubleAnimation Storyboard.TargetProperty="StrokeDashOffset" From="0" To="-6"/>
                             </Storyboard>
                         </BeginStoryboard>
                     </DataTrigger.EnterActions>
                     <DataTrigger.ExitActions>
                         <StopStoryboard BeginStoryboardName="flowBegin"/>
                     </DataTrigger.ExitActions>
                 </DataTrigger>
             </Style.Triggers>
         </Style>
     </Path.Style>
     <Path.Data>
         <GeometryGroup>
             <LineGeometry StartPoint="30 0" EndPoint="20 216"/>
             <LineGeometry StartPoint="70 0" EndPoint="70 216"/>
             <LineGeometry StartPoint="110 0" EndPoint="110 216"/>
             <LineGeometry StartPoint="150 0" EndPoint="150 216"/>
             <LineGeometry StartPoint="190 0" EndPoint="190 216"/>
             <LineGeometry StartPoint="230 0" EndPoint="240 216"/>
         </GeometryGroup>
     </Path.Data>
 </Path>
文章来源:https://blog.csdn.net/jjailsa/article/details/135491590
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。