创建动画面临的第一个挑战是为动画选择正确的属性。期望的结果(例如,在窗口中移动元素)与需要使用的属性(在这种情况下是Canvas.Left和Canvas.Top属性)之间的关系并不总是很直观。下面是一些指导原则:

  • 如果希望使用动画来使元素显示和消失,不要使用Visibility属性(该属性只能在完全可见和完全不可见之间进行切换)。应改用Opacity属性淡入或淡出元素。
  • 如果希望动态改变元素的位置,可考虑使用Canvas面板。它提供了最直接的属性(Canvas.Left及Canvas.Top),而且开销最小。此外,也可使用动画属性在其他布局容器中获得类似效果。例如,可通过使用ThicknessAnimation类动态改变Margin和Padding等属性,还可动态改变Grid控件中的MinWidth或MinHeight属性、一列或一行。
  • 动画最常用的属性是渲染变换。可使用变换移动或翻转元素(TranslateTransform)、旋转元素(RotateTransform)、缩放或扭曲元素(ScaleTransform)等。通过仔细地使用变换,有时可避免在动画中硬编码尺寸和位置。它们也绕过了WPF布局系统,比直接作用于元素大小或位置的其他方法速度更快。
  • 动态改变元素表面的较好方法是修改画刷属性。可使用ColorAnimation改变颜色或其他动画对象来变换更复杂画刷的属性,如渐变中的偏移。
  • 接下来的示例演示了如何动态改变变换和画刷,以及如何使用更多的一些动画类型。还将讨论如何使用关键帧创建多段动画、如何创建基于路径的动画和基于帧的动画。

    一、动态变换

    变换提供了自定义元素的最强大方式之一。当使用变换时,不只是改变元素的边界,而且会移动、翻转、扭曲、拉伸、放大、缩小或旋转元素的整个可视化外观。例如,可通过ScaleTransform动态改变按钮的尺寸,这会改变整个按钮的尺寸,包括按钮的边框及其内部的内容。这种效果比动态改变Width和Height属性或改变文本的Fontsize属性给人的印象更深刻。

    前面章节了解到,每个元素都能以两种不同的方式使用变换:RenderTransform属性和LayoutTransform属性。RenderTransform效率更高,因为是在布局之后应用变换并且勇于变换最终的渲染输出。LayoutTransform在布局前应用,从而其他控件需要重新排列以适应变换。改变LayoutTransform属性会引发新的布局操作(除非在Canvas面板上使用元素,在这种情况下,RenderTransform和LayoutTransform的效果相同)。

    为在动画中使用变换,第一步是定义变换(动画可改变已经存在的变换,但不能创建新的变换)。例如,假设希望使按钮旋转,此时需要使用RotateTransform对象:

    <Button Content="A Button">
        <RenderTransform>
            <RotateTransform></RotateTransform>
        </RenderTransform>
    </Button>

    现在当将鼠标移动到按钮上时,下面的事件触发器就会旋转按钮。使用的目标属性是RenderTransform.Angle——换句话说,读取按钮的RenderTransform属性并修改其中定义的RotateTransform对象的Angle属性。事实上,RenderTransform属性可包含各种不同的变换对象,每种变换对象的属性各不相同,这不会引起问题。只要使用的变换具有Angle属性,这个触发器就能工作:

    <EventTrigger RoutedEvent="Button.MouseEnter">
        <EventTrigger.Actions>
              <BeginStoryboard Name="rotateStoryboardBegin">
                   <Storyboard>
                         <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Angle"
                     To="360" Duration="0:0:0.8" RepeatBehavior="Forever"></DoubleAnimation>
                    </Storyboard>
               </BeginStoryboard>
         </EventTrigger.Actions>
    </EventTrigger>

    按钮在0.8秒得时间内旋转一周并且持续旋转。当按钮旋转时仍完全可用——例如,可单击按钮并处理Click事件。

    为保证按钮绕其中心旋转(而不是绕左上角旋转),需要按如下方式设置RenderTransformOrigin属性:

    <Button RenderTransformOrigin="0.5,0.5"/>

    请记住,RenderTransformOrigin属性使用0~1的相对单位,所以0.5表示中点。

    为停止旋转,可使用第二个触发器响应MouseLeave事件。这是,可删除执行旋转的故事板,但这会导致按钮一步调回到它原来的位置。更好的方法是开始第二个动画,用它替代第一个动画。这个动画忽略To和From属性,这意味着它无缝地再0.2秒得时间内将按钮旋转回原始方向:

    <EventTrigger RoutedEvent="Button.MouseLeave">
        <EventTrigger.Actions>
             <BeginStoryboard>
                  <Storyboard>
                      <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Angle"
                       Duration="0:0:0.2"></DoubleAnimation>
                   </Storyboard>
              </BeginStoryboard>
        </EventTrigger.Actions>
    </EventTrigger>

    为创建旋转的按钮,需要为Button.Triggers集合添加这两个触发器。或将它们(以及变换)放到一个样式中,并根据需要为多个按钮应用这个样式。例如,下面的窗口标记充满了下图中显示的“能旋转的”按钮:

    <Window x:Class="Animation.RotateButton"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="RotateButton" Height="300" Width="300">
        <Window.Resources>
            <Style TargetType="{x:Type Button}">
                <Setter Property="HorizontalAlignment" Value="Center"></Setter>
                <Setter Property="RenderTransformOrigin" Value="0.5,0.5"></Setter>
                <Setter Property="Padding" Value="20,15"></Setter>
                <Setter Property="Margin" Value="2"></Setter>
                <Setter Property="RenderTransform">
                    <Setter.Value>
                        <RotateTransform></RotateTransform>
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <EventTrigger RoutedEvent="Button.MouseEnter">
                        <EventTrigger.Actions>
                            <BeginStoryboard Name="rotateStoryboardBegin">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Angle"
                     To="360" Duration="0:0:0.8" RepeatBehavior="Forever"></DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="Button.MouseLeave">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="RenderTransform.Angle"
                       Duration="0:0:0.2"></DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                </Style.Triggers>
            </Style>
        </Window.Resources>
        <StackPanel Margin="5" Button.Click="cmd_Clicked">
            <Button>One</Button>
            <Button>Two</Button>
            <Button>Three</Button>
            <Button>Four</Button>
            <TextBlock Name="lbl" Margin="5"></TextBlock>
        </StackPanel>
    </Window>
    RotateButton

    在单击任何按钮时,都会在TextBlock元素中显示一条信息。

    这个示例还未分析渲染变换和布局变换之间的区别提供了绝佳的机会。如果修改代码可使用LayoutTransform属性,那么会发现当旋转其中一个按钮时,其他按钮会被推离原来的位置。例如,如果旋转最上面的按钮,下面的按钮会上下跳动以避开顶部的按钮。

    <Window x:Class="Animation.RotateButtonWithLayout"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="RotateButtonWithLayout" Height="300" Width="300">
        <Window.Resources>
            <Style TargetType="{x:Type Button}">
                <Setter Property="HorizontalAlignment" Value="Center"></Setter>
                <Setter Property="RenderTransformOrigin" Value="0.5,0.5"></Setter>
                <Setter Property="Padding" Value="20,15"></Setter>
                <Setter Property="Margin" Value="2"></Setter>
                <Setter Property="LayoutTransform">
                    <Setter.Value>
                        <RotateTransform></RotateTransform>
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <EventTrigger RoutedEvent="Button.MouseEnter">
                        <EventTrigger.Actions>
                            <BeginStoryboard Name="rotateStoryboardBegin">
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="LayoutTransform.Angle"
                     To="360" Duration="0:0:0.8" RepeatBehavior="Forever"></DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="Button.MouseLeave">
                        <EventTrigger.Actions>
                            <!-- <RemoveStoryboard BeginStoryboardName="rotateStoryboardBegin"></RemoveStoryboard> -->
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="LayoutTransform.Angle"
                       Duration="0:0:0.2"></DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                </Style.Triggers>
            </Style>
        </Window.Resources>
        <StackPanel Margin="5" Button.Click="cmd_Clicked">
            <Button>One</Button>
            <Button>Two</Button>
            <Button>Three</Button>
            <Button>Four</Button>
            <TextBlock Name="lbl" Margin="5"></TextBlock>
        </StackPanel>
    </Window>
    RotateButtonWithLayout

    动态改变多个变换

    可很容易地组合使用变换。实际上这是很容易——只需要使用TransformGroup对象设置LayoutTransform或RenderTransform属性即可。可根据需要在TransformGroup对象中嵌套任意多个变换。

    下图显示了一个使用两个变换创建的有趣效果。文档窗口刚开始作为主窗口左上角的小缩略图。当文档窗口显示时,内容旋转、扩展并快速淡入到试图中,从概念上讲,这与最大化窗口时Windows使用的效果类似。在WPF中,可通过变换为所有的元素应用这种技巧。

    为创建这种效果,在如下TransformGroup对象中定义了两个变换,并使用TransformGroup对象设置包含所有内容的Board对象的RenderTransform属性:

    <Border.RenderTransform>
         <TransformGroup>
               <ScaleTransform></ScaleTransform>
                <RotateTransform></RotateTransform>
          </TransformGroup>
    </Border.RenderTransform>

    通过指定数字偏移值(0用于首先显示的ScaleTransform对象,1用于接下来显示的RotateTransform对象),动画可与这两个变换对象进行交互。例如,下面的动画放大内容:

    <DoubleAnimation Storyboard.TargetName="element"
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                    From="0" To="1" Duration="0:0:2" AccelerationRatio="1"></DoubleAnimation>
    <DoubleAnimation Storyboard.TargetName="element"
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    From="0" To="1" Duration="0:0:2" AccelerationRatio="1"></DoubleAnimation>

    下面的动画位于相同的故事板中,用于旋转内容:

    <DoubleAnimation Storyboard.TargetName="element"
                                    Storyboard.TargetProperty="RenderTransform.Children[1].Angle"
                                    From="70" To="0" Duration="0:0:2" ></DoubleAnimation>

    这个动画中的内容比此处显示的内容还多。例如,还有一个同事增加Opacity属性的动画,并且当Borad元素达到最大尺寸时,它短暂地向后"反弹"一下,创建一种更趋自然的效果。为这个动画创建时间线并修改各个动画对象属性需要耗费时间——理想情况下,可使用诸如Expression Blend的设计工具执行这些任务,而不是通过手动编写代码来完成这些任务。甚至更好的情况下,只要有第三方开发者将这一逻辑分组到自定义动画中,就可以重用并根据需要将其应用到对象上(根据目前的情况,可通过将Storyboard对象存储为应用程序级的资源,重用这个动画)。

    下面是完整的XAML标记:

    <Window x:Class="Animation.ExpandElement"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="ExpandElement" Height="423.2" Width="488.8" WindowStartupLocation="CenterScreen">
        <Window.Triggers>
            <EventTrigger RoutedEvent="Window.Loaded">
                <EventTrigger.Actions>
                    <BeginStoryboard>
                        <Storyboard SpeedRatio="1.5">
                            <DoubleAnimation Storyboard.TargetName="element"
                                    Storyboard.TargetProperty="Opacity"
                                    From="0.2" To="1" Duration="0:0:2.5"></DoubleAnimation>
                            <DoubleAnimation Storyboard.TargetName="element"
                                    Storyboard.TargetProperty="RenderTransform.Children[1].Angle"
                                    From="70" To="0" Duration="0:0:2" ></DoubleAnimation>
                            <DoubleAnimation Storyboard.TargetName="element"
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                    From="0" To="1" Duration="0:0:2" AccelerationRatio="1"></DoubleAnimation>
                            <DoubleAnimation Storyboard.TargetName="element"
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    From="0" To="1" Duration="0:0:2" AccelerationRatio="1"></DoubleAnimation>
                            <DoubleAnimation Storyboard.TargetName="element"
                                                Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                                To="0.98" BeginTime="0:0:2" Duration="0:0:0.05"  DecelerationRatio="1"></DoubleAnimation>
                            <DoubleAnimation Storyboard.TargetName="element"
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    To="0.98" BeginTime="0:0:2" Duration="0:0:0.05" DecelerationRatio="1"></DoubleAnimation>
                            <DoubleAnimation Storyboard.TargetName="element"
                                                Storyboard.TargetProperty="RenderTransform.Children[0].ScaleX"
                                                To="1" BeginTime="0:0:2.05" Duration="0:0:0.2"  AccelerationRatio="1"></DoubleAnimation>
                            <DoubleAnimation Storyboard.TargetName="element"
                                    Storyboard.TargetProperty="RenderTransform.Children[0].ScaleY"
                                    To="1" BeginTime="0:0:2.05" Duration="0:0:0.2" AccelerationRatio="1"></DoubleAnimation>
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger.Actions>
            </EventTrigger>
        </Window.Triggers>
            <!--<Button Name="element">
            <Button.Content>Text</Button.Content>
            <Button.RenderTransform>
              <TransformGroup>
                <ScaleTransform ScaleX="0" ScaleY="0"></ScaleTransform>
                <TranslateTransform></TranslateTransform>
                <RotateTransform Angle="90"></RotateTransform>
              </TransformGroup>
            </Button.RenderTransform>
          </Button>-->
            <Border  Name="element" Margin="3" Background="LightGoldenrodYellow"
                   BorderBrush="DarkBlue" BorderThickness="2" CornerRadius="5" >
                <Border.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform></ScaleTransform>
                        <RotateTransform></RotateTransform>
                    </TransformGroup>
                </Border.RenderTransform>
                <FlowDocumentScrollViewer IsToolBarVisible="True">
                    <FlowDocument>
                        <Paragraph xml:space="preserve">The <Italic>foof</Italic> feature is indispensable. You can configure the foof feature using the Foof Options dialog box.</Paragraph>
                        <BlockUIContainer>
                            <Button HorizontalAlignment="Left" Padding="5">Open Foof Options</Button>
                        </BlockUIContainer>
                        <Paragraph FontSize="20pt">Largest Cities in the Year 100</Paragraph>
                        <Table>
                            <Table.Columns>
                                <TableColumn Width="*"></TableColumn>
                                <TableColumn Width="3*"></TableColumn>
                                <TableColumn Width="*"></TableColumn>
                            </Table.Columns>
                            <TableRowGroup  >
                                <TableRow FontWeight="Bold" >
                                    <TableCell >
                                        <Paragraph>Rank</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Name</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Population</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>1</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Rome</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>450,000</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>2</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Luoyang (Honan), China</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>420,000</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>3</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Seleucia (on the Tigris), Iraq</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>250,000</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>4</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Alexandria, Egypt</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>250,000</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>5</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Antioch, Turkey</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>150,000</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>6</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Anuradhapura, Sri Lanka</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>130,000</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>7</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Peshawar, Pakistan</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>120,000</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>8</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Carthage, Tunisia</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>100,000</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>9</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Suzhou, China</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>n/a</Paragraph>
                                    </TableCell>
                                </TableRow>
                                <TableRow>
                                    <TableCell>
                                        <Paragraph>10</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>Smyrna, Turkey</Paragraph>
                                    </TableCell>
                                    <TableCell>
                                        <Paragraph>90,000</Paragraph>
                                    </TableCell>
                                </TableRow>
                            </TableRowGroup>
                        </Table>
                    </FlowDocument>
                </FlowDocumentScrollViewer>
            </Border>
        </Grid>
    </Window>
    ExpandElement

    这种效果非常有用。例如,可使用该效果将注意力吸引到新的内容——例如用户刚刚打开的文件。这种效果可能的变化是无穷无尽的。例如,创建产品目录时,当用户将鼠标悬停在相应的产品名称上时,滑入包含产品细节的面板或将产品图像滚入试图。

    二、动态改变画刷

    动态改变画刷是WPF动画中的另一种常用技术,和动态变换同样容易。同样,这种技术使用恰当的动画类型,深入到希望改变的特定子属性。

    下图显示了一个修改RadialGradientBrush画刷的示例。当动画运行时,径向渐变的中心点沿椭圆漂移,从而实现了一种三维效果。同时,外侧的渐变颜色从蓝色变成黑色。

    为实现这个效果,需要使用两种尚未分析过的动画类型。ColorAnimation动画在两个颜色之间逐渐混合,创建一种微妙的颜色转移效果。PointAnimation动画可将点从一个位置移到另一个位置(本质上与使用独立DoubleAnimation,通过线性插值同时修改X坐标和Y坐标是相同的)。可使用PointAnimation动画改变使用点构造的图形,或者就像这个示例中那样,改变径向渐变中心点的位置。

    下面是标记定义了椭圆及其画刷:

    <Ellipse Name="ellipse" Margin="5" Grid.Row="1" Stretch="Uniform">
       <Ellipse.Fill>
             <RadialGradientBrush
                 RadiusX="1" RadiusY="1" GradientOrigin="0.7,0.3">
                        <GradientStop Color="White" Offset="0" ></GradientStop>
                        <GradientStop Color="Blue" Offset="1" ></GradientStop>
              </RadialGradientBrush>
       </Ellipse.Fill>
    </Ellipse>

    下面是移动中心点以及改变第二种颜色的两个动画:

    <PointAnimationUsingKeyFrames Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Fill.GradientOrigin"
                                               RepeatBehavior="Forever">
                            <LinearPointKeyFrame Value="0.7,0.3" KeyTime="0:0:0"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.3,0.7" KeyTime="0:0:5"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.5,0.9" KeyTime="0:0:8"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.9,0.6" KeyTime="0:0:10"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.8,0.2" KeyTime="0:0:12"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.7,0.3" KeyTime="0:0:14"></LinearPointKeyFrame>
                            <DiscretePointKeyFrame Value="0.7,0.3" KeyTime="0:0:20"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.3,0.7" KeyTime="0:0:25"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.5,0.9" KeyTime="0:0:28"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.9,0.6" KeyTime="0:0:20"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.8,0.2" KeyTime="0:0:22"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.7,0.3" KeyTime="0:0:24"></DiscretePointKeyFrame>
                        </PointAnimationUsingKeyFrames>
                        <ColorAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Fill.GradientStops[1].Color"
                  To="Black"  Duration="0:0:10"
                  AutoReverse="True" RepeatBehavior="Forever"></ColorAnimation>

    本例网站XAML标记:

    <Window x:Class="Animation.AnimateRadialGradient"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="AnimateRadialGradient" Height="300" Width="300" WindowStartupLocation="CenterScreen">
        <Window.Triggers>
            <EventTrigger RoutedEvent="Window.Loaded">
                <BeginStoryboard>
                    <Storyboard>
                        <PointAnimationUsingKeyFrames Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Fill.GradientOrigin"
                                               RepeatBehavior="Forever">
                            <LinearPointKeyFrame Value="0.7,0.3" KeyTime="0:0:0"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.3,0.7" KeyTime="0:0:5"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.5,0.9" KeyTime="0:0:8"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.9,0.6" KeyTime="0:0:10"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.8,0.2" KeyTime="0:0:12"></LinearPointKeyFrame>
                            <LinearPointKeyFrame Value="0.7,0.3" KeyTime="0:0:14"></LinearPointKeyFrame>
                            <DiscretePointKeyFrame Value="0.7,0.3" KeyTime="0:0:20"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.3,0.7" KeyTime="0:0:25"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.5,0.9" KeyTime="0:0:28"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.9,0.6" KeyTime="0:0:20"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.8,0.2" KeyTime="0:0:22"></DiscretePointKeyFrame>
                            <DiscretePointKeyFrame Value="0.7,0.3" KeyTime="0:0:24"></DiscretePointKeyFrame>
                        </PointAnimationUsingKeyFrames>
                        <ColorAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="Fill.GradientStops[1].Color"
                  To="Black"  Duration="0:0:10"
                  AutoReverse="True" RepeatBehavior="Forever"></ColorAnimation>
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger>
        </Window.Triggers>
            <Ellipse Name="ellipse" Margin="5" Grid.Row="1" Stretch="Uniform">
                <Ellipse.Fill>
                    <RadialGradientBrush
                 RadiusX="1" RadiusY="1" GradientOrigin="0.7,0.3">
                        <GradientStop Color="White" Offset="0" ></GradientStop>
                        <GradientStop Color="Blue" Offset="1" ></GradientStop>
                    </RadialGradientBrush>
                </Ellipse.Fill>
            </Ellipse>
        </Grid>
    </Window>
    AnimateRadialGradient

    通过修改LinearGradientBrush和RadialGradientBrush画刷的颜色和偏移值可创建许多精彩效果。如果还不够,渐变画刷还有自己的RelativeTransform属性,可使用该属性旋转、缩放、拉伸以及扭曲画刷。WPF团队有一个有趣的称为Gradient Obsession的工具,该工具用于构建基于渐变的动画。

    VisualBrush画刷

    VisualBrush画刷可获取任意元素的外观,使用该外观可填充另一个表面。其他表面可以是任何内容,从普遍的矩形到文本框中的字母。

    下图显示了一个基本示例。顶部是一个真实的活动按钮。下面通过VisualBrush画刷使用按钮图片填充一个矩形,并通过各种变换效果拉伸并旋转按钮图片。

    VisualBrush画刷还为实现一些有趣的动画效果的动画效果提供了可能。例如,不仅可动态显示活动的真实元素,还可动态显示具有相同填充内容的简单矩形。

    为理解这种方法的工作原理,分析这个示例,该例将一个元素放入试图中。当这个动画运行时,处理具有动画的元素的方法和处理其他任意WPF元素的方式相同,这意味着可单击它内部的按钮,或使用键盘滚动内容(如果用户的操作足够迅速的话)。在一些情况下,这可能会令用户感到困惑。在由写情况下,这可能导致性能下降,因为需要额外的开销来变换输入(如鼠标单击),并且和原始元素一起传递输入。

    使用VisualBrush画刷可轻易地代替这种效果。首先,需要创建另一个元素,使用VisualBrush画刷填充该元素。VisualBrush画刷必须根据希望包含动画的元素(在这个示例中,是名为visual的边框)绘制可视化内容。

    <Rectangle Grid.Row="1" Name="rectangle" Width="100" Stretch="Uniform" ClipToBounds="False" RenderTransformOrigin="0.5,0.5">
                <Rectangle.Fill>
                    <VisualBrush Visual="{Binding ElementName=visual}">
                    </VisualBrush>
                </Rectangle.Fill>
                <Rectangle.RenderTransform>
                    <TransformGroup>
                        <SkewTransform CenterX="0.5"></SkewTransform>
                        <RotateTransform CenterX="0.5" CenterY="0.5"></RotateTransform>
                    </TransformGroup>
                </Rectangle.RenderTransform>
            </Rectangle>

    示例完整XAML标记:

    <Window x:Class="Animation.AnimateVisual"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="AnimateVisual" Height="300" Width="300" Background="LightGoldenrodYellow" WindowStartupLocation="CenterScreen">
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Button Name="visual" HorizontalAlignment="Center" VerticalAlignment="Center">
                <Button.Content>Test</Button.Content>
                <Button.Triggers>
                    <EventTrigger RoutedEvent="Button.Click">
                        <BeginStoryboard>
                            <Storyboard  RepeatBehavior="Forever">
                                <DoubleAnimation Storyboard.TargetName="rectangle"
                                     Storyboard.TargetProperty="RenderTransform.Children[0].AngleY"
                                     To="180" Duration="0:0:15" AutoReverse="True"></DoubleAnimation>
                                <DoubleAnimation Storyboard.TargetName="rectangle"
                                     Storyboard.TargetProperty="RenderTransform.Children[1].Angle"
                                     To="180" Duration="0:0:20" AutoReverse="True"></DoubleAnimation>
                                <DoubleAnimation Storyboard.TargetName="rectangle"
                                     Storyboard.TargetProperty="Opacity"
                                     To="0.1" Duration="0:0:4" AutoReverse="True"></DoubleAnimation>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Button.Triggers>
            </Button>
            <Rectangle Grid.Row="1" Name="rectangle" Width="100" Stretch="Uniform" ClipToBounds="False" RenderTransformOrigin="0.5,0.5">
                <Rectangle.Fill>
                    <VisualBrush Visual="{Binding ElementName=visual}">
                    </VisualBrush>
                </Rectangle.Fill>
                <Rectangle.RenderTransform>
                    <TransformGroup>
                        <SkewTransform CenterX="0.5"></SkewTransform>
                        <RotateTransform CenterX="0.5" CenterY="0.5"></RotateTransform>
                    </TransformGroup>
                </Rectangle.RenderTransform>
            </Rectangle>
        </Grid>
    </Window>
    AnimateVisual

    为将矩形放到与原始元素相同的位置,可将它们同时放到Grid面板的同一个单元格中。改变单元格的尺寸,使其适合原始元素(边框),并拉伸矩形使其相匹配。另一个选择是在真实实用程序上覆盖Canvas面板(然后可将动画属性绑定到下面真实元素的ActualWidth和ActualHeight属性,从而确保对齐)。

    添加矩形后,只需要调整动画来执行动态变化。最后,当动画完成时隐藏矩形:

    private void storyboardCompleted(object sender,EventArgs e)
        rectangle.Visibility=Visibility.Collapsed;
    

    三、动态改变像素着色器

      通过“【WPF学习】第四十六章 效果 ”的学习,了解了像素着色器(可为任意元素应用位图风格效果的低级例程,如模糊、辉光以及弯曲效果)的相关内容。就自身而言,像素着色器是一些有趣并且偶尔有用的工具。但通过结合使用动画,他们可变的更通用。可使用它们设计吸引眼球的过渡效果(例如,通过模糊控件使其淡出、隐藏,然后模糊另一个控件使其淡入)。也可使用像素着色器创建给人留下深刻印象的用户交互效果(例如,当用户将鼠标移动到按钮上时增加按钮上的辉光)。最后为像素着色器的属性应用动画,就像为其他内容应用动画一样容易。

      下图显示的页面是基于在前面给出的旋转按钮示例构建的。该例包含一系列按钮,并且当用户将鼠标移动到其中某个按钮上时,关联并开始动画。区别在于这个示例中的动画不是旋转按钮,而将模糊半径减少至0。结果是移动鼠标时,最近的按钮骤然轻快地变得清晰。

      该例的代码和旋转按钮示例中的代码相同。需要为每个按钮提供BlurEffect对象而不是RotateTransform对象:

    <Setter Property="Effect">
         <Setter.Value>
                <BlurEffect Radius="10"></BlurEffect>
         </Setter.Value>
    </Setter>

      还需要相应地修改动画:

     <EventTrigger RoutedEvent="Button.MouseEnter">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="Effect.Radius"
             To="0" Duration="0:0:0.4"></DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="Button.MouseLeave">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="Effect.Radius" To="10"
            Duration="0:0:0.2"></DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>

      本实例完整XAML标记:

    <Window x:Class="Animation.BlurringButtons"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="BlurringButtons" Height="300" Width="300" WindowStartupLocation="CenterScreen">
        <Window.Resources>
            <Style TargetType="{x:Type Button}">
                <Setter Property="HorizontalAlignment" Value="Center"></Setter>
                <Setter Property="RenderTransformOrigin" Value="0.5,0.5"></Setter>
                <Setter Property="Padding" Value="20,15"></Setter>
                <Setter Property="Margin" Value="2"></Setter>
                <Setter Property="Effect">
                    <Setter.Value>
                        <BlurEffect Radius="10"></BlurEffect>
                    </Setter.Value>
                </Setter>
                <Style.Triggers>
                    <EventTrigger RoutedEvent="Button.MouseEnter">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="Effect.Radius"
             To="0" Duration="0:0:0.4"></DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="Button.MouseLeave">
                        <EventTrigger.Actions>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Storyboard.TargetProperty="Effect.Radius" To="10"
            Duration="0:0:0.2"></DoubleAnimation>
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger.Actions>
                    </EventTrigger>
                </Style.Triggers>
            </Style>
        </Window.Resources>
        <StackPanel Margin="5">
            <Button>One</Button>
            <Button>Two</Button>
            <Button>Three</Button>
            <Button>Four</Button>
            <TextBlock Name="lbl" Margin="5"></TextBlock>
        </StackPanel>
    </Window>
    BlurringButtons

      为反向使用相同的方法来突出显示按钮。例如,可使用应用辉光效果的像素着色器突出显示鼠标在其上悬停的按钮。

    作者:Peter Luo
    本文版权归作者和博客园共有,欢迎转载,但必须给出原文链接,并保留此段声明,否则保留追究法律责任的权利。