WPF中的動畫

作者: 周銀輝  來源: 博客園  發布時間: 2009-02-27 15:07  閱讀: 5685 次  推薦: 0   原文鏈接   [收藏]  

動畫無疑是WPF中最吸引人的特色之一,其可以像Flash一樣平滑地播放并與程序邏輯進行很好的交互。這里我們討論一下故事板。

在WPF中我們采用Storyboard(故事板)的方式來編寫動畫,為了對Storyboard有個大概的印象,你可以粘貼以下代碼到XamlPad來查看效果:


<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提供一種簡單的“漸變”動畫,我們為一個Animation指定開始值和一個結束值,并指定由開始值到達結束值所需的時間,便可形成一個簡單的動畫。比如我們指定長方形的寬度由100變化到200,所需時間為1秒,很容易想像這樣的動畫是什么樣的,而它對應的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));

代碼里我們定義了一個DoubleAnimation,并指定了它的開始值和結束值以及它由開始值到達結束值所需的時間。至于后面兩句,它們是用來將Aniamtion與指定的對象和指定的屬性相關聯,等會我們將介紹。
注意到,這里我們使用的是DoubleAnimation,因為我們所要變化的是數值。那么如果我們要變化顏色是不是就用ColorAnimation了呢,對,其實出了這些之外還有PointAnimation等等,并且你可以實現IAnimatable接口來實現自定義版本的Animation。關于這些你可以參見System.Windows.MediaAniamtion名字空間.

但值得注意的是并非每個屬性都能夠使用Animation,它必須滿足以下條件:
1,它必須是Dependency Property
2,它所在類必須繼承于DependencyObject,必須實現了IAnimatable接口.
3,必須有類型一致的Animation Type(即Color類型使用ColorAniamtion,Point類型使用PointAnimation等)

一個簡單的Animation定義了一個簡單的動畫,很容易想到的是,如果若干個Animation同時作用于一個對象,那么這個對象不就可以表現復雜的動畫了嗎,對,這就是Storyboard

Storyboard可以看做是Animation的容器,它包含了若干的簡單動畫來完成一個復雜動畫。
參考以下代碼:


<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來變化矩形的寬度,并定義了ColorAnimation來變化矩形的顏色。

至此,你已經可以編寫絢麗的WPF動畫了,并推薦你下載Expression Blend來制作WPF動畫.



但你會發現使用XAML標記的方式來編寫動畫雖然很簡單,但缺乏了C#等程序設計語言的靈活性,比如我們的矩形動畫中矩形的寬度是由后臺邏輯計算出來的變量值,我們的動畫將如何編寫呢,這時我更喜歡使用C#的方式來編寫動畫,雖然這所需的代碼量更大.
以下重點介紹如何用C#編寫動畫,并且這更助于你理解Storyboard是如何工作的。

參考以下代碼:

            this.Name = "PageMain";
            myRectangle.Name 
= "MyRectangle";  
            
            NameScope.SetNameScope(
thisnew 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));
定義了一個DoubleAniamtion,并指定了它的開始值和結束值以及所需的時間.

Storyboard.SetTargetName(myDoubleAnimation, myRectangle.Name);設置myDoubleAniamtion的作用對象是"myRectangle",注意到傳入的第二個參數是一個字符串myRectangle.Name,那么我們的程序怎么知道"myRectangle"這個字符串就是指我們的矩形對象myRectangle呢,這里存在一個名稱與對象的映射,即我們的"myRectangle"映射到矩形對象myRectangle,為了構造這個映射我們涉及到了NameScope(名字域)這個概念.
            NameScope.SetNameScope(thisnew NameScope());                      
            
this.RegisterName(myRectangle.Name, myRectangle);
上面的代碼中,this設置了一個名字域,myRectagle向這個名字域注冊了自己的名字,這樣我們的程序就可以通過this的名字域來查找到myRectangle與"myRectangle"之間的映射關系了,關于NameScope可以參見MSDN
WPF Namescopes主題.
為了讓myDoubleAnimation知道它所作用的屬性是誰,我們使用
Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(Rectangle.WidthProperty));語句來將Aniamtion與屬性關聯起來,其中PropertyPath中指定要作用的對象所對應的DependencyProperty.
然后我們將定義好的myDoubleAniamtion添加到myStoryboard的Children中去.最后就可以通過調用Storyboard的Begin(FrameworkElement)方法來開始我們的動畫.

Begin方法的另一個重載形式是
public void Begin (FrameworkContentElement containingObject,bool isControllable),第二個參數表明我們的storyboard是否是可控的,如果可控的話,我們可以像控制播放器一樣控制來控制storyboard,關于控制Storyboard請參考Storyboard類中的Pause,Seek等方法.

至此也許我們會認為這些知識足以應付簡單的動畫了,現在讓我們一起設計一個簡單的動畫,也許會發現些問題.

假設我們的界面中存在一個Button對象button1,我們設計一個簡單的動畫讓它在窗口中的x坐標從0連續變化到100,然后在從100變化到0,如此重復.也許我們會編寫如下的代碼:

            this.button1.Name = "button1";
            
this.Name = "window1";
            NameScope.SetNameScope(
thisnew 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, 
???);

但當我們編寫到Storyboard.SetTargetProperty(xAnimation, ???);時發現似乎不知道將我們的xAniamtion關聯到哪個屬性.似乎Button中沒有用來控制X坐標的DependencyProperty.但通過研究后發現(你可以通過ExpressionBlend自動生成的XAML代碼來發現這些信息),如果我們將button1的RenderTransform設置為TranslateTransform,然后可以通過TranslateTransform的XProperty屬性來更改button1的X坐標.注意到,我們并不是像以前一樣直接關聯到Button的某個屬性(比如先前的WidthProperty),而是通過其RenderTransformProperty屬性的XProperty來間接關聯的,這中方式叫做"屬性鏈"(PropertyChain).
參考下面的代碼:

            DependencyProperty[] propertyChain = new DependencyProperty[]
            {
                Button.RenderTransformProperty,
                TranslateTransform.XProperty
            };

            Storyboard.SetTargetProperty(xAnimation, 
new PropertyPath("(0).(1)", propertyChain));
為了構造PropertyChain,我們先定義一個DependencyProperty的數組,注意數組的元素是怎么來的,它按照屬性鏈的"鏈條"關系依次書寫,直到到達我們最終要修改的屬性,(由于我們是通過將RenderTransformProperty設置為TranslateTransform類型,所以第二個元素是TranslateTransform.XProperty),簡單地說就是(類型1.屬性1,類型2.屬性2,....類型n.屬性n),其中類型i是屬性i-1的類型或可以與之轉換的類型.
這樣我們的代碼就演化如下:
            this.button1.RenderTransform = new TranslateTransform();

            
this.button1.Name = "button1";
            
this.Name = "window1";
            NameScope.SetNameScope(
thisnew 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);
注意:如果你收到關于PropertyChain的運行時錯誤或動畫沒有效果,那么你應該初始化button的RenderTransform屬性,所以我們添加了this.button1.RenderTransform = new TranslateTransform();語句.

更多的,請參見Windows SDK "Storyboard Overview"
示例程序下載

 

0
0
 
標簽:WPF
 
 

文章列表

全站熱搜
創作者介紹
創作者 大師兄 的頭像
大師兄

IT工程師數位筆記本

大師兄 發表在 痞客邦 留言(0) 人氣()