動(dòng)畫(huà)無(wú)疑是WPF中最吸引人的特色之一,其可以像Flash一樣平滑地播放并與程序邏輯進(jìn)行很好的交互。這里我們討論一下故事板。
在WPF中我們采用Storyboard(故事板)的方式來(lái)編寫(xiě)動(dòng)畫(huà),為了對Storyboard有個(gè)大概的印象,你可以粘貼以下代碼到XamlPad來(lái)查看效果:
<!-- This example shows how to animate with a storyboard.-->
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowTitle="Storyboards Example">
<StackPanel Margin="20">
<Rectangle Name="MyRectangle"
Width="100"
Height="100">
<Rectangle.Fill>
<SolidColorBrush x:Name="MySolidColorBrush" Color="Blue" />
</Rectangle.Fill>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Page.Loaded">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever" AutoReverse="True">
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Width"
From="100" To="200" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</StackPanel>
</Page>
在介紹Storyboard之前應該先了解Animation
Animation提供一種簡(jiǎn)單的“漸變”動(dòng)畫(huà),我們?yōu)橐粋€(gè)Animation指定開(kāi)始值和一個(gè)結束值,并指定由開(kāi)始值到達結束值所需的時(shí)間,便可形成一個(gè)簡(jiǎn)單的動(dòng)畫(huà)。比如我們指定長(cháng)方形的寬度由100變化到200,所需時(shí)間為1秒,很容易想像這樣的動(dòng)畫(huà)是什么樣的,而它對應的Xaml代碼如下:
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Width"
From="100" To="200" Duration="0:0:1" /> 將它翻譯成C#代碼則如下:
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 100;
myDoubleAnimation.To = 200;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty)); 代碼里我們定義了一個(gè)DoubleAnimation,并指定了它的開(kāi)始值和結束值以及它由開(kāi)始值到達結束值所需的時(shí)間。至于后面兩句,它們是用來(lái)將Aniamtion與指定的對象和指定的屬性相關(guān)聯(lián),等會(huì )我們將介紹。
注意到,這里我們使用的是DoubleAnimation,因為我們所要變化的是數值。那么如果我們要變化顏色是不是就用ColorAnimation了呢,對,其實(shí)出了這些之外還有PointAnimation等等,并且你可以實(shí)現IAnimatable接口來(lái)實(shí)現自定義版本的Animation。關(guān)于這些你可以參見(jiàn)System.Windows.MediaAniamtion名字空間.
但值得注意的是并非每個(gè)屬性都能夠使用Animation,它必須滿(mǎn)足以下條件:
1,它必須是Dependency Property
2,它所在類(lèi)必須繼承于DependencyObject,必須實(shí)現了IAnimatable接口.
3,必須有類(lèi)型一致的Animation Type(即Color類(lèi)型使用ColorAniamtion,Point類(lèi)型使用PointAnimation等)
一個(gè)簡(jiǎn)單的Animation定義了一個(gè)簡(jiǎn)單的動(dòng)畫(huà),很容易想到的是,如果若干個(gè)Animation同時(shí)作用于一個(gè)對象,那么這個(gè)對象不就可以表現復雜的動(dòng)畫(huà)了嗎,對,這就是Storyboard
Storyboard可以看做是Animation的容器,它包含了若干的簡(jiǎn)單動(dòng)畫(huà)來(lái)完成一個(gè)復雜動(dòng)畫(huà)。
參考以下代碼:
<!-- This example shows how to animate with a storyboard.-->
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
WindowTitle="Storyboards Example">
<StackPanel Margin="20">
<Rectangle Name="MyRectangle"
Width="100"
Height="100">
<Rectangle.Fill>
<SolidColorBrush x:Name="MySolidColorBrush" Color="Blue" />
</Rectangle.Fill>
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Page.Loaded">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever" AutoReverse="True">
<DoubleAnimation
Storyboard.TargetName="MyRectangle"
Storyboard.TargetProperty="Width"
From="100" To="200" Duration="0:0:1" />
<ColorAnimation
Storyboard.TargetName="MySolidColorBrush"
Storyboard.TargetProperty="Color"
From="Blue" To="Red" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</StackPanel>
</Page>
這里我們的Storyboard定義了DoubleAnimation來(lái)變化矩形的寬度,并定義了ColorAnimation來(lái)變化矩形的顏色。
至此,你已經(jīng)可以編寫(xiě)絢麗的WPF動(dòng)畫(huà)了,并推薦你下載Expression Blend來(lái)制作WPF動(dòng)畫(huà).
但你會(huì )發(fā)現使用XAML標記的方式來(lái)編寫(xiě)動(dòng)畫(huà)雖然很簡(jiǎn)單,但缺乏了C#等程序設計語(yǔ)言的靈活性,比如我們的矩形動(dòng)畫(huà)中矩形的寬度是由后臺邏輯計算出來(lái)的變量值,我們的動(dòng)畫(huà)將如何編寫(xiě)呢,這時(shí)我更喜歡使用C#的方式來(lái)編寫(xiě)動(dòng)畫(huà),雖然這所需的代碼量更大.
以下重點(diǎn)介紹如何用C#編寫(xiě)動(dòng)畫(huà),并且這更助于你理解Storyboard是如何工作的。
參考以下代碼:
this.Name = "PageMain";
myRectangle.Name = "MyRectangle";
NameScope.SetNameScope(this, new NameScope());
this.RegisterName(myRectangle.Name, myRectangle);
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 100;
myDoubleAnimation.To = 200;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty));
Storyboard myStoryboard = new Storyboard();
myStoryboard.Children.Add(myDoubleAnimation);
this.Loaded += delegate(object sender, MouseEventArgs e)
{
myStoryboard.Begin(this);
};
其中:
DoubleAnimation myDoubleAnimation = new DoubleAnimation();
myDoubleAnimation.From = 100;
myDoubleAnimation.To = 200;
myDoubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
定義了一個(gè)DoubleAniamtion,并指定了它的開(kāi)始值和結束值以及所需的時(shí)間.
Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);設置myDoubleAniamtion的作用對象是"myRectangle",注意到傳入的第二個(gè)參數是一個(gè)字符串myRectangle.Name,那么我們的程序怎么知道"myRectangle"這個(gè)字符串就是指我們的矩形對象myRectangle呢,這里存在一個(gè)名稱(chēng)與對象的映射,即我們的"myRectangle"映射到矩形對象myRectangle,為了構造這個(gè)映射我們涉及到了NameScope(名字域)這個(gè)概念.
NameScope.SetNameScope(this, new NameScope());
this.RegisterName(myRectangle.Name, myRectangle);
上面的代碼中,this設置了一個(gè)名字域,myRectagle向這個(gè)名字域注冊了自己的名字,這樣我們的程序就可以通過(guò)this的名字域來(lái)查找到myRectangle與"myRectangle"之間的映射關(guān)系了,關(guān)于NameScope可以參見(jiàn)MSDN WPF Namescopes主題.
為了讓myDoubleAnimation知道它所作用的屬性是誰(shuí),我們使用Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty));語(yǔ)句來(lái)將Aniamtion與屬性關(guān)聯(lián)起來(lái),其中PropertyPath中指定要作用的對象所對應的DependencyProperty.
然后我們將定義好的myDoubleAniamtion添加到myStoryboard的Children中去.最后就可以通過(guò)調用Storyboard的Begin(FrameworkElement)方法來(lái)開(kāi)始我們的動(dòng)畫(huà).
Begin方法的另一個(gè)重載形式是public void Begin (FrameworkContentElement containingObject,bool isControllable),第二個(gè)參數表明我們的storyboard是否是可控的,如果可控的話(huà),我們可以像控制播放器一樣控制來(lái)控制storyboard,關(guān)于控制Storyboard請參考Storyboard類(lèi)中的Pause,Seek等方法.
至此也許我們會(huì )認為這些知識足以應付簡(jiǎn)單的動(dòng)畫(huà)了,現在讓我們一起設計一個(gè)簡(jiǎn)單的動(dòng)畫(huà),也許會(huì )發(fā)現些問(wèn)題.
假設我們的界面中存在一個(gè)Button對象button1,我們設計一個(gè)簡(jiǎn)單的動(dòng)畫(huà)讓它在窗口中的x坐標從0連續變化到100,然后在從100變化到0,如此重復.也許我們會(huì )編寫(xiě)如下的代碼:
this.button1.Name = "button1";
this.Name = "window1";
NameScope.SetNameScope(this, new NameScope());
this.RegisterName(this.button1.Name, this.button1);
DoubleAnimation xAnimation = new DoubleAnimation();
xAnimation.From = 0;
xAnimation.To = 100;
xAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
Storyboard story = new Storyboard();
story.AutoReverse = true;
story.RepeatBehavior = RepeatBehavior.Forever;
story.Children.Add(xAnimation);
Storyboard.SetTargetName(xAnimation, this.button1.Name);
Storyboard.SetTargetProperty(xAnimation, ???);
但當我們編寫(xiě)到
Storyboard.SetTargetProperty(xAnimation, ???);時(shí)發(fā)現似乎不知道將我們的xAniamtion關(guān)聯(lián)到哪個(gè)屬性.似乎Button中沒(méi)有用來(lái)控制X坐標的DependencyProperty.但通過(guò)研究后發(fā)現(你可以通過(guò)ExpressionBlend自動(dòng)生成的XAML代碼來(lái)發(fā)現這些信息),如果我們將button1的RenderTransform設置為T(mén)ranslateTransform,然后可以通過(guò)TranslateTransform的XProperty屬性來(lái)更改button1的X坐標.注意到,我們并不是像以前一樣直接關(guān)聯(lián)到Button的某個(gè)屬性(比如先前的WidthProperty),而是通過(guò)其RenderTransformProperty屬性的XProperty來(lái)間接關(guān)聯(lián)的,這中方式叫做"屬性鏈"(PropertyChain).
參考下面的代碼:
DependencyProperty[] propertyChain = new DependencyProperty[]
{
Button.RenderTransformProperty,
TranslateTransform.XProperty
};
Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(0).(1)", propertyChain));
為了構造PropertyChain,我們先定義一個(gè)DependencyProperty的數組,注意數組的元素是怎么來(lái)的,它按照屬性鏈的"鏈條"關(guān)系依次書(shū)寫(xiě),直到到達我們最終要修改的屬性,(由于我們是通過(guò)將RenderTransformProperty設置為T(mén)ranslateTransform類(lèi)型,所以第二個(gè)元素是TranslateTransform.XProperty),簡(jiǎn)單地說(shuō)就是(類(lèi)型1.屬性1,類(lèi)型2.屬性2,....類(lèi)型n.屬性n),其中類(lèi)型i是屬性i-1的類(lèi)型或可以與之轉換的類(lèi)型.
這樣我們的代碼就演化如下: this.button1.RenderTransform = new TranslateTransform();
this.button1.Name = "button1";
this.Name = "window1";
NameScope.SetNameScope(this, new NameScope());
this.RegisterName(this.button1.Name, this.button1);
DoubleAnimation xAnimation = new DoubleAnimation();
xAnimation.From = 0;
xAnimation.To = 100;
xAnimation.Duration = new Duration(TimeSpan.FromSeconds(1));
DependencyProperty[] propertyChain = new DependencyProperty[]
{
Button.RenderTransformProperty,
TranslateTransform.XProperty
};
Storyboard story = new Storyboard();
story.AutoReverse = true;
story.RepeatBehavior = RepeatBehavior.Forever;
story.Children.Add(xAnimation);
Storyboard.SetTargetName(xAnimation, this.button1.Name);
Storyboard.SetTargetProperty(xAnimation, new PropertyPath("(0).(1)", propertyChain));
story.Begin(this);
注意:如果你收到關(guān)于PropertyChain的運行時(shí)錯誤或動(dòng)畫(huà)沒(méi)有效果,那么你應該初始化button的RenderTransform屬性,所以我們添加了this.button1.RenderTransform = new TranslateTransform();語(yǔ)句.