WPF之小动画一
定义动画:
直接使用Element进行BeginAnimation
DoubleAnimation animation = new DoubleAnimation(); animation.By = 100; animation.Duration = TimeSpan.FromSeconds(1); btnTest.BeginAnimation(Button.WidthProperty, animation);
也可以将Animation添加到StoryBoard中去,这样可以一次执行多个动画:
Storyboard sb = new Storyboard(); DoubleAnimation animation = new DoubleAnimation(); animation.By = 100; animation.Duration = TimeSpan.FromSeconds(1); Storyboard.SetTarget(animation, btnTest); Storyboard.SetTargetProperty(animation, new PropertyPath("Width")); sb.Children.Add(animation); sb.Begin();
我们称Storyboard为动画面板,顾名思义就是专门来执行动画的,可以向其Children中添加多个Animation,这样在Begin的时候就可以执行多个动画。
也可以使用Trigger触发器来触发动画:
<Button Width="200" Height="50" Name="btnTest" Content="动画"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Width"
To="{Binding ElementName=window,Path=Width}" Duration="0:0:2"></DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Button.Triggers> </Button>
EventTrigger表示事件触发器,RoutedEvent表示哪个事件的时候触发对应的Action,当前是Button.Click事件,当点击按钮则会执行Storyboard中的动画。在这里使用了一个BeginStoryboard,这样就不用手动对Storyboard调用Begin方法。
在Style中使用触发器Trigger:
FrameElement.Triggers仅支持EventTrigger,还好有其它的触发器可以使用,那就是Styles.Triggers、DataTemplate.Triggers、ControlTemplate.Triggers它们支持属性触发、数据触发以及事件触发。
<Style TargetType="Button"> <Style.Triggers> <Trigger Property="Button.IsMouseOver" Value="True"> <Trigger.EnterActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Width" To="500" Duration="0:0:3"></DoubleAnimation> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Width" To="200" Duration="0:0:3"></DoubleAnimation> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> </Style.Triggers> </Style>
上述代码是在样式中使用Trigger,Style没有指定Key,仅指定了TargetType,则表示对所有类型应用此样式。在Style中添加IsMouseOver时候的触发器,即当鼠标悬浮时执行,Property表示元素的属性变化,可以是IsPressed或者IsMouseOver等。EnterActions表示鼠标悬浮时候执行,将Width动画执行到500;ExitActions表示鼠标离开时候执行重新将Width设置为原始;然后运行程序鼠标选择则执行Width变大动画,离开则复原。
动画的重叠:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Page.Resources> <!-- This Style specifies mouseover and mouseout behaviors. The button gets larger when the cursor moves over it and smaller when the cursor moves away. Note that the same Properties (ScaleX and ScaleY) are being targeted by both animations. The BeginStoryboard for each animation uses a HandoffBehavior of "Compose" which causes the old animation to interpolate more gradually into the new one. --> <Style x:Key="ButtonWithCompose" TargetType="{x:Type Button}"> <Setter Property="Button.RenderTransform"> <Setter.Value> <ScaleTransform CenterX="50" CenterY="50" ScaleX="1" ScaleY="1" /> </Setter.Value> </Setter> <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard > <Storyboard> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleX" To="3" /> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleY" To="3" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard HandoffBehavior="Compose"> <Storyboard> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleX" /> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleY" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> <!-- For this button style, BeginStoryboard uses the default HandoffBehavior of "SnapShotAndReplace" --> <Style x:Key="ButtonWithSnapShotAndReplace" TargetType="{x:Type Button}"> <Setter Property="Button.RenderTransform"> <Setter.Value> <ScaleTransform CenterX="50" CenterY="50" ScaleX="1" ScaleY="1" /> </Setter.Value> </Setter> <Style.Triggers> <EventTrigger RoutedEvent="Mouse.MouseEnter"> <EventTrigger.Actions> <BeginStoryboard > <Storyboard> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleX" To="3" /> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleY" To="3" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="Mouse.MouseLeave"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleX" /> <DoubleAnimation Duration="0:0:2" Storyboard.TargetProperty="RenderTransform.ScaleY" /> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> </Page.Resources> <Canvas> <Button Style="{StaticResource ButtonWithSnapShotAndReplace}" Canvas.Top="200" Canvas.Left="200" Width="100" Height="100"> SnapShotAndReplace </Button> <Button Style="{StaticResource ButtonWithCompose}" Canvas.Top="200" Canvas.Left="400" Width="100" Height="100"> Compose </Button> </Canvas> </Page>
使用BeginStoryboard的HandoffBehavior 属性进行设置,此属性一共两个值可选;Compose 将通过把新动画追加到组合链的末尾来组合新动画和现有动画;
SnapshotAndReplace 新动画将替换它们所应用到的属性上的任何现有动画。
当鼠标光标移到这两个按钮上时,它们将变大,当鼠标光标移开时,它们将变小。 如果将鼠标移到一个按钮上然后快速移走光标,则在第一个动画完成之前将应用第二个动画。 当两个动画像这样重叠时,您可以看到 Compose 和 SnapshotAndReplace 的 HandoffBehavior 值之间存在差异。 如果值为 Compose,在出现动画重叠时,动画之间的过渡将较为平滑,而如果值为 SnapshotAndReplace,则会使新动画立即取代之前的重叠动画。
注:上文代码来自MSDN
控制动画:
PauseStoryboard 暂停动画;
ResumeStoryboard 恢复暂停的动画;
StopStoryboard 停止动画;
SeekStoryboard 将动画定位到指定位置;
SetStoryboardSpeedRatio 设置动画速度比;
SkipStoryboardToFill 将动画跳转至结束;
RemoveStoryboard 删除动画。
控制动画有两个代码实现方式,一种是使用Trigger一种是通过后置代码实现。
<Window x:Class="WpfApplication3.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" Title="MainWindow" Name="window" Height="400" Width="500"> <Window.Triggers> <EventTrigger SourceName="btnStart" RoutedEvent="Button.Click"> <BeginStoryboard Name="testBeginStory"> <Storyboard Name="testStory"> <DoubleAnimation Storyboard.TargetName="imgDay" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:10">
</DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger SourceName="btnPause" RoutedEvent="Button.Click"> <PauseStoryboard BeginStoryboardName="testBeginStory"></PauseStoryboard> </EventTrigger> <EventTrigger SourceName="btnResume" RoutedEvent="Button.Click"> <ResumeStoryboard BeginStoryboardName="testBeginStory"></ResumeStoryboard> </EventTrigger> <EventTrigger SourceName="btnStop" RoutedEvent="Button.Click"> <StopStoryboard BeginStoryboardName="testBeginStory"></StopStoryboard> </EventTrigger> <EventTrigger SourceName="btnSeek" RoutedEvent="Button.Click"> <SeekStoryboard BeginStoryboardName="btnSeek" Offset="0:0:5"></SeekStoryboard> </EventTrigger> </Window.Triggers> <StackPanel> <Grid> <Image Source="night.jpg" Width="400" Height="300" Stretch="Fill"></Image> <Image Source="day.jpg" Name="imgDay" Width="400" Height="300" Stretch="Fill"></Image> </Grid> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <Button Content="开始" Height="30" Width="50" Name="btnStart" Margin="5"></Button> <Button Content="暂停" Height="30" Width="50" Name="btnPause" Margin="5"></Button> <Button Content="继续" Height="30" Width="50" Name="btnResume" Margin="5"></Button> <Button Content="停止" Height="30" Width="50" Name="btnStop" Margin="5"></Button> <Button Content="跳转至中间" Height="30" Width="70" Name="btnSeek" Margin="5"></Button> </StackPanel> </StackPanel> </Window>
代码中有两个图片,一个是白天,一个是黑夜。通过动画修改白天图片的透明度一直到0,然后显示黑夜的图片,这是一个完整的动画。
在Windows.Triggers中放置了五个事件触发器,分别对应五个按钮,通过设置EventTrigger的SourceName为按钮的名称,RoutedEvent同样为Click事件,在每一个EventTrigger中放置对应的Storyboard的操作,每个Storyboard操作的BeginStoryboardName就是BeginStory的Name。有一个特殊就是SeekStory中的Offset表示将动画定位到指定的偏移量,也就是指定的动画时间点。
通过后置代码实现的方式是使用StoryBoard的方法进行实现,大家可以查询MSDN。
注:虽然我们可以去操作动画,但是当动画开始之后属性是无法直接修改的,比如速度比,这些属性都是在XAML中配置的,所以如果我们修改了这个属性也是无效的,好在有对应的函数可以调用
修改上述代码片段,在外层StackPanel中添加一个Slider,用于控制速度比。
<Slider Grid.Column="1" Name="sldSpeed" Minimum="0" Maximum="3" Value="1" TickPlacement="BottomRight" TickFrequency="0.1" ValueChanged="sldSpeed_ValueChanged_1"></Slider>
private void sldSpeed_ValueChanged_1(object sender, RoutedPropertyChangedEventArgs<double> e) { testStory.SetSpeedRatio(this, sldSpeed.Value); }
testStory为Storyboard对象,使用SetSpeedRatio设置速度比,第一个参数为动画的最高层容器,当前就是窗体所以是this,第二个就是一个double的值,当前使用的是silder的value。
监视动画过程:
通过专门的方法得到动画的当前执行时间(动画的整个Duration)和当前的进度(0--1),详情见下文。
Storyboar的事件:
Completed | 动画完成 |
CurrentGlobalSpeedInvalidated | 暂停、继续、反转、加速、减速、查找、停止或更改时钟的交互速度可触发此事件 |
CurrentStateInvalidated | 当状态发生变化的时候发生(启动、停止或填充时) |
CurrentTimeInvalidated | 当执行时间点发生变化时触发 |
RemoveRequested | 当动画被移除 |
在此例子中我们要使用到的事件是CurrentTimeInvalidated,当时间点变化就触发此事件。首先,我们继续修改XAML代码添加一个Label和一个ProgressBar,Label显示当前的时间,ProgressBar显示进度。代码修改如下:
<Label Name="lblTime" HorizontalContentAlignment="Center"></Label> <ProgressBar Name="progressbar" Height="30" Margin="0 5 0 0" Minimum="0" Maximum="1"></ProgressBar>
事件代码如下:
private void testStory_CurrentTimeInvalidated_1(object sender, EventArgs e) { Clock storyboardeClock = sender as Clock; if (storyboardeClock.CurrentProgress == null) { lblTime.Content = "[Stop]"; progressbar.Value = 0; } else { lblTime.Content = storyboardeClock.CurrentTime.ToString(); progressbar.Value = storyboardeClock.CurrentProgress.Value; } }
事件的第一个参数sender为Clock对象,表示TimeLine类型的运行时间状态。通过GetCurrentTime()和GetCurrentProgress()得到当前动画的执行点和当前动画的执行进度。