silverlight做一些复杂动画时,不可能所有的动画都事先用Blend之类的设计工具"画"好(或者在设计期就在vs里编好),很多时候我们希望在运行时能动态控制动画,或者凭空动态创建一段动画.
sl3.0的官方sdk文档里有一节"以编程方式使用动画"讲的就是这个,今天研究了下整理分析于此:
对于事先"画"好(或者称之为在设计期准备好的动画),我们可以在运行时通过名字获取动画引用,进而改变某些属性:
1.示例1(代码来自sdk,以下同),运行时动态改变动画的To属性值,从而实现鼠标点击跟随效果
Xaml部分:
<UserControl x:Class="AnimationControl.Change"
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"
mc:Ignorable="d" >
<Canvas MouseLeftButtonDown="Handle_MouseDown" Background="DarkSeaGreen" Width="400" Height="300" Cursor="Hand">
<Canvas.Resources>
<Storyboard x:Name="myStoryboard">
<PointAnimation x:Name="myPointAnimation" Storyboard.TargetProperty="Center" Storyboard.TargetName="MyAnimatedEllipseGeometry" Duration="0:0:0.5"/>
</Storyboard>
</Canvas.Resources>
<TextBlock Text="请在圆形之外的空白处点击" Foreground="White" FontStretch="Normal" FontWeight="Bold" FontSize="18" TextAlignment="Center" Canvas.Left="100" Canvas.Top="130" Cursor="Hand" Opacity="0.5">
</TextBlock>
<Path Fill="Blue">
<Path.Data>
<EllipseGeometry x:Name="MyAnimatedEllipseGeometry" Center="200,100" RadiusX="15" RadiusY="15" />
</Path.Data>
</Path>
</Canvas>
</UserControl>布局很简单,一个Canvas上放了一个圆,并创建了一个动画myPointAnimation
CS部分:
1
using System.Windows;2
using System.Windows.Controls;3
using System.Windows.Input;4

5
namespace AnimationControl6


{7
public partial class Change : UserControl8

{9
public Change()10

{11
InitializeComponent();12
}13

14
private void Handle_MouseDown(object sender, MouseButtonEventArgs e)15

{16
//取得鼠标当前在Canvas中的点击坐标17
double newX = e.GetPosition(sender as UIElement).X;18
double newY = e.GetPosition(sender as UIElement).Y;19
Point myPoint = new Point();20
myPoint.X = newX;21
myPoint.Y = newY;22

23
//动态设置动画的To属性值24
myPointAnimation.To = myPoint;25

26
//播放27
myStoryboard.Begin();28
}29

30
}31
}32

代码不长,一看就明,获取鼠标的点击坐标后,赋值为动画myPointAnimation的To属性(即移动后的目标坐标值),然后播放
2.示例2,有时候很多对象可能会引用到同一效果的动画,每个对象都去创建一个动画太浪费,这时候我们可以把类似的动画通过改变TartgetName值得以重用
但有一点要注意:因为同一个动画同一时间只能有一个Target,所以如果给这个动画赋值了TartgetName,并且该动画正在播放的过程中,又用代码给动画的TargetName属性赋值另外一个对象,并要求播放,显示是会失效的。(实际测试中发现,虽然这样不会抛出任何异常)
为避免这种错误的发生,sdk中的示例代码提示我们可以这样做:
Xaml部分:
1
<UserControl x:Class="AnimationControl.Change2"2
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"3
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"4
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"5
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"6
mc:Ignorable="d"7
>8

9
<StackPanel Orientation="Horizontal">10
<StackPanel.Resources>11
<Storyboard x:Name="myStoryboard1" Completed="Storyboard_Completed">12
<DoubleAnimation x:Name="myDoubleAnimation1" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:2" AutoReverse="True" />13
</Storyboard>14
<Storyboard x:Name="myStoryboard2" Completed="Storyboard_Completed">15
<DoubleAnimation x:Name="myDoubleAnimation2" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:2" AutoReverse="True" />16
</Storyboard>17
<Storyboard x:Name="myStoryboard3" Completed="Storyboard_Completed">18
<DoubleAnimation x:Name="myDoubleAnimation3" Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" Duration="0:0:2" AutoReverse="True" />19
</Storyboard>20
</StackPanel.Resources>21
<Rectangle x:Name="MyAnimatedRectangle1" Margin="3" Width="90" Height="100" Fill="Blue" MouseLeftButtonDown="Start_Animation" Cursor="Hand" />22
<Rectangle x:Name="MyAnimatedRectangle2" Margin="3" Width="90" Height="100" Fill="Blue" MouseLeftButtonDown="Start_Animation" Cursor="Hand" />23
<Rectangle x:Name="MyAnimatedRectangle3" Margin="3" Width="90" Height="100" Fill="Blue" MouseLeftButtonDown="Start_Animation" Cursor="Hand" />24
<Rectangle x:Name="MyAnimatedRectangle4" Margin="3" Width="90" Height="100" Fill="Blue" MouseLeftButtonDown="Start_Animation" Cursor="Hand" />25
</StackPanel>26
27
</UserControl>28

StackPanel中横向放了4个矩形,同时放置了三个完全相同的double型动画(用来让对象的透明度从1变到0,即渐渐淡去),实现目的:4个矩形,3个动画,显示按照一一对应的默认原则,总会有一个矩形无法分配到动画,如何实现重用呢?看下面的
cs部分:
1
using System;2
using System.Windows.Controls;3
using System.Windows.Input;4
using System.Windows.Media.Animation;5
using System.Windows.Shapes;6

7
namespace AnimationControl8


{9
public partial class Change2 : UserControl10

{11
public Change2()12

{13
InitializeComponent();14
}15

16
bool storyboard1Active = false;17
bool storyboard2Active = false;18
bool storyboard3Active = false;19
20
private void Start_Animation(object sender, MouseEventArgs e)21

{22
//得到被点击的矩形对象引用23
Rectangle myRect = (Rectangle)sender;24

25
if (!storyboard1Active)26

{27
myStoryboard1.Stop();28
myDoubleAnimation1.SetValue(Storyboard.TargetNameProperty, myRect.Name);29
myStoryboard1.Begin();30
storyboard1Active = true;31
}32
else if (!storyboard2Active)33

{34
myStoryboard2.Stop();35
myDoubleAnimation2.SetValue(Storyboard.TargetNameProperty, myRect.Name);36
myStoryboard2.Begin();37
storyboard2Active = true;38
}39
else if (!storyboard3Active)40

{41
myStoryboard3.Stop();42
myDoubleAnimation3.SetValue(Storyboard.TargetNameProperty, myRect.Name);43
myStoryboard3.Begin();44
storyboard3Active = true;45
}46
}47

48
private void Storyboard_Completed(object sender, EventArgs e)49

{50
Storyboard myStoryboard = sender as Storyboard;51
switch (myStoryboard.GetValue(NameProperty).ToString())52

{53
case "myStoryboard1": storyboard1Active = false; break;54
case "myStoryboard2": storyboard2Active = false; break;55
case "myStoryboard3": storyboard3Active = false; break;56
}57
}58

59
}60
}61

这里注意:定义了三个标识变量,用于标识每个动画是否正在播放中,如果播放完成后该变量为false,否则为true(即正在播放),这个每个矩形上点击请求播放动画时,总是优先找到空闲(即处于播放状态)的动画,然后为该动画赋值TargetName属性并播放,同时播放途中把对应的标识变量改成true,以防止播放过程中被人修改TargetName值
也许有人会问了:如果没找到空闲的动画,不是没效果了?Yes,你猜对了,如果快速依次点击4个矩形,会发现最后一次点击没什么变化。这种情况就要用到下面提到的代码动态创建动画了
3。示例3 代码动态创建动画
理解起来很简单,代码创建动画对象,并让其播放。
xaml部分:
1
<UserControl x:Class="AnimationControl.Create"2
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"3
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"4
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"5
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"6
mc:Ignorable="d"7
>8

9
<Canvas Name="LayoutRoot" Background="DarkOliveGreen" Width="400" Height="300">10
<TextBlock Text="点击我将动态创建一段动画" MouseLeftButtonDown="TextBlock_MouseLeftButtonDown" Foreground="White" FontStretch="Normal" FontWeight="Bold" FontSize="18" TextAlignment="Center" Canvas.Left="100" Canvas.Top="130" Cursor="Hand" Opacity="0.5">11
12
</TextBlock>13
</Canvas>14
</UserControl>15

一个几乎是空的Canvas,没啥特别的
再来看cs部分:
1
using System;2
using System.Windows;3
using System.Windows.Controls;4
using System.Windows.Media;5
using System.Windows.Media.Animation;6
using System.Windows.Shapes;7

8
namespace AnimationControl9


{10
public partial class Create : UserControl11

{12
public Create()13

{14
15
InitializeComponent();16
17
}18

19
public void CreateAnimation()20

{21
//创建一个矩形22
Rectangle myRectangle = new Rectangle();23
myRectangle.Width = 50;24
myRectangle.Height = 50; 25
myRectangle.Fill = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0));26

27
//把矩形加入到Canvas中28
LayoutRoot.Children.Add(myRectangle);29

30
//创建二个double型的动画,并设定播放时间为2秒31
Duration duration = new Duration(TimeSpan.FromSeconds(2)); 32
DoubleAnimation myDoubleAnimation1 = new DoubleAnimation();33
DoubleAnimation myDoubleAnimation2 = new DoubleAnimation();34

35
myDoubleAnimation1.Duration = duration;36
myDoubleAnimation2.Duration = duration;37

38
//创建故事版,并加入上面的二个double型动画39
Storyboard sb = new Storyboard();40
sb.Duration = duration;41

42
sb.Children.Add(myDoubleAnimation1);43
sb.Children.Add(myDoubleAnimation2);44

45
//设置动画的Target目标值46
Storyboard.SetTarget(myDoubleAnimation1, myRectangle);47
Storyboard.SetTarget(myDoubleAnimation2, myRectangle);48

49
//设置动画的变化属性50
Storyboard.SetTargetProperty(myDoubleAnimation1, new PropertyPath("(Canvas.Left)"));51
Storyboard.SetTargetProperty(myDoubleAnimation2, new PropertyPath("(Canvas.Top)"));52

53
myDoubleAnimation1.To = 200;54
myDoubleAnimation2.To = 200;55

56
if (!LayoutRoot.Resources.Contains("unique_id"))57

{58
//将动画版加入Canvas资源,注意:这里的unique_id必须是资源中没有的唯一键59
LayoutRoot.Resources.Add("unique_id", sb);60
sb.Completed += new EventHandler(sb_Completed);61

62
//播放63
sb.Begin();64
}65
else66

{67
sb = null;68
LayoutRoot.Children.Remove(myRectangle);69
}70

71
72

73
}74

75
void sb_Completed(object sender, EventArgs e)76

{77
LayoutRoot.Resources.Remove("unique_id");//播放完成后,移除资源,否则再次点击时将报错78
}79

80
private void TextBlock_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)81

{82
CreateAnimation();83
}84
}85
}86

几乎所有关键的地方,都加了注释了应该能容易看明白
这里有一点要注意:创建动画的代码,必须放在构造函数中的InitializeComponent()之后调用,原因很简单,如果组件尚未初始化完毕,这时向根容器加入一些动态创建的元件当然会报错。