創建動畫面臨的第一個挑戰是為動畫選擇正確的屬性。期望的結果(例如,在視窗中移動元素)與需要使用的屬性(在這種情況下是Canvas.Left和Canvas.Top屬性)之間的關係並不總是很直觀。下麵是一些指導原則: 如果希望使用動畫來使元素顯示和消失,不要使用Visibility屬性(該屬性只能在完全 ...
創建動畫面臨的第一個挑戰是為動畫選擇正確的屬性。期望的結果(例如,在視窗中移動元素)與需要使用的屬性(在這種情況下是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> <Grid> <!--<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>