Wpf Design And Animation Lab
這是一個 WPF 專案,用於創作及收集一些好玩的設計和動畫。目前已有數十個 Demo,部分 Demo 有相關部落格介紹詳細的實作步驟和原理:
透過這些部落格,你將會了解到如何實現一些酷酷的 WPF 動畫和設計,以及一些 WPF 的技術細節。
1. 已實現的設計和動畫
1.1 使用三種方式實現弧形進度條

實現弧形進度條的方案有很多種,透過用 Path 和 ArcSegment、Arc、Ellipse 這三個方案,可以了解各種 Shape 的基本用法。
1.2 僅用 Rectangle 實現圓柱形進度條

圓柱形進度條不難實現,不過有趣的是上圖完全由代表矩形的 Rectangle 組成,這稍微有點反直覺。
首先我們需要重溫一些基礎知識:Rectangle 顯示帶圓角的矩形。用 RadiusX 和 RadiusY 可分別指定用於使矩形的角變圓的橢圓的 X 軸和 Y 軸半徑。
如下面這個例子,可以看到 RadiusX="50" RadiusY="20" 的 Rectangle 的圓角和 Width="100" Height="40" 的 Ellipse (X 軸半徑 50,Y 軸半徑 20 )完全重合在一起。
<Rectangle Height="100"
Width="100"
Fill="#FF7E9EC0"
Stroke="#FFFF0EC4"
StrokeThickness="5"
RadiusX="50"
RadiusY="20" />
<Ellipse HorizontalAlignment="Left"
VerticalAlignment="Top"
StrokeThickness="5"
Stroke="Yellow"
Fill="Red"
Width="100"
Height="40"
Opacity="0.5" />

現在我們把上面的 Rectangle 拉長,就成了圓柱的基本形狀;反過來將它壓扁,就成了圓柱的截面。再把它們設定成半透明的,就成了圓柱形進度條的背景:

<Grid.Resources>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="#36a8e2" />
<Setter Property="RadiusX" Value="25" />
<Setter Property="RadiusY" Value="5" />
</Style>
</Grid.Resources>
<Rectangle Opacity="0.2" />
<Rectangle Height="10"
VerticalAlignment="Top"
Opacity="0.1" />
之後再添加一層半透明的漸層,以及另一個截面,就完成了圓柱形的進度條。
1.3 玩玩彩虹文字及動畫
用 ItemsControl 拆分文字實現彩虹文字是一個很好玩的方案,因為可以對每個文字做不同的變形和動畫,實現很多種玩法。首先,因為 string 是個集合,其實它可以用作 ItemsControl 的 ItemsSource。但在 XAML 上直接寫 ItemsSource="somestring"` 會報錯,可以用 ContentControl 包裝一下,寫成這樣:
<ContentControl Content="ItemsControl" >
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<ItemsControl ItemsSource="{TemplateBinding Content}" >
</ItemsControl>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
然後設定 ItemsControl 的 ItemsPanel,讓內容橫向排列;設定 DataTemplate,讓拆分後的字元顯示在 TextBlock 上:
<ItemsControl ItemsSource="{TemplateBinding Content}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
接下來,為了讓每個字元顯示不同的顏色,需要實作一個 Collection 類別並在 XAML 上實例化它,將用到的顏色放進去:
<common:RepeatCollection x:Key="RepeatCollection">
<SolidColorBrush>#4a0e68</SolidColorBrush>
<SolidColorBrush>#b62223</SolidColorBrush>
<SolidColorBrush>#fdd70c</SolidColorBrush>
<SolidColorBrush>#f16704</SolidColorBrush>
<SolidColorBrush>#69982d</SolidColorBrush>
<SolidColorBrush>#0075a5</SolidColorBrush>
<SolidColorBrush>#0b0045</SolidColorBrush>
</common:RepeatCollection>
這個 RepeatCollection 的程式碼如下,它其實是個循環佇列,每次呼叫 Next 的 getter 方法就拿取下一個元素(叫 CircleCollection 會不會好些?):
public class RepeatCollection : Collection<object>
{
private int _offset;
public object Next
{
get
{
if (this.Count == 0)
return null;
var result = this[_offset];
_offset++;
if (_offset > this.Count - 1)
_offset = 0;
return result;
}
}
}
最後,TextBlock 的 Foreground 繫結到集合的 Next 屬性,實現每一個 TextBlock 都使用不同的顏色:
<TextBlock Foreground="{Binding Next, Source={StaticResource RepeatCollection}}" Text="{Binding}" />

修改一下上面的程式碼,就可以實現彩虹文字的動畫:

1.4 製作一個彩虹按鈕
將 LinearGradientBrush 應用在文字上,文字就變成了彩虹色。如果兩個 GradientStop 之間 Color 相同就不會發生漸層,如果兩個 GradientStop 之間 Offset 就會馬上變。利用這種手法,再加上我使用了等寬字型,所以可以製造出每個字顏色不一樣的彩虹文字:
<LinearGradientBrush x:Name="RainbowBrush" StartPoint="0,0.5" EndPoint="1,.5">
<GradientStop x:Name="G1" Offset="0" Color="#65b849" />
<GradientStop x:Name="G2" Offset=".166" Color="#65b849" />
<GradientStop x:Name="G3" Offset=".166" Color="#f7b423" />
<GradientStop x:Name="G4" Offset=".3333" Color="#f7b423" />
<GradientStop x:Name="G5" Offset="0.3333" Color="#f58122" />
<GradientStop x:Name="G6" Offset="0.5" Color="#f58122" />
<GradientStop x:Name="G7" Offset="0.5" Color=" #f8f8f8" />
<GradientStop x:Name="G8" Offset="0.5" Color=" #f8f8f8" />
<GradientStop x:Name="G9" Offset="0.50" Color="#de3a3c" />
<GradientStop x:Name="G10" Offset="0.666" Color="#de3a3c" />
<GradientStop x:Name="G11" Offset="0.666" Color="#943f96" />
<GradientStop x:Name="G12" Offset="0.8633" Color="#943f96" />
<GradientStop x:Name="G13" Offset="0.8633" Color="#009fd9" />
<GradientStop x:Name="G14" Offset="01" Color="#009fd9" />
</LinearGradientBrush>

在 MouseOver 的 Storyboard 裡控制 LinearGradientBrush 改變方向。有兩種方式可以改變它的方向,其中一種是用 PointAnimation 改變 StartPoint 和 EndPoint,另一種是用 DoubleAnimation 直接改變 LinearGradientBrush.RelativeTransform。後一種的寫法如下:
<Storyboard>
<DoubleAnimation Storyboard.TargetName="textBlock"
Storyboard.TargetProperty="(TextBlock.Foreground).(Brush.RelativeTransform).(RotateTransform.Angle)"
To="90"
Duration="0:0:0.5">
<DoubleAnimation.EasingFunction>
<QuarticEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
<LinearGradientBrush x:Name="RainbowBrush" StartPoint="0,0.5" EndPoint="1,.5">
<LinearGradientBrush.RelativeTransform>
<RotateTransform Angle="0" CenterX="0.5" CenterY="0.5" />
</LinearGradientBrush.RelativeTransform>
運行起來的效果就是將所有顏色旋轉 90 度,看起來就像以前的 Apple 的 Logo 配色。在上面的 LinearGradientBrush 裡,我偷偷藏了兩個白色的 GradientStop (名為 G6 和 G7 那兩個),它們的 Offset 都是 0.5,處於正中間的位置。在按鈕的 Pressed 狀態中,用 DoubleAnimation 將它們前後的所有 GradientStop 的 Offset 都設定為 0 或 1,效果是將所有顏色向兩邊推。因為現在旋轉了 90 度,所以實際上是向上下兩個方向推:

1.5 實現兩個任天堂 Switch 的載入動畫


用拆分文字和 TimeSpanIncreaser 的方案,實現了兩個在任天堂 Switch 中最常見的動畫。
1.6 使用 Shazzam Shader Editor 編寫一個 Lighten Effect
在上面的動畫裡為了實現不同亮度的 Grid,使用了一個 LightenConverter 類別,但是它只能處理 SolidColorBrush。為了可以應用在更多場合,接下來自己寫一個 Effect 來實現相同 Lighten 的效果。

1.7 實現 WPF 的 Inner Shadow
在 WPF 中,我們通常用 DropShadow 做陰影效果,但都是做外陰影。內陰影(Inner Shadow)的話其實也不是不可以,就是有些曲折。實現內陰影的方案有幾種,其中我最喜歡用另一個元素的 VisualBrush 來做 OpacityMask 的方案。
<Grid Width="100"
Height="100"
Margin="10">
<Rectangle x:Name="Rectangle2"
Fill="White"
RadiusX="8"
RadiusY="8" />
<Border Margin="0">
<Border.Effect>
<DropShadowEffect BlurRadius="8" ShadowDepth="0" />
</Border.Effect>
<ContentControl HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="OpacityMask" />
</Border>
<Grid.OpacityMask>
<VisualBrush Stretch="None" Visual="{Binding ElementName=Rectangle2}" />
</Grid.OpacityMask>
</Grid>
但這樣做出來的陰影都不會太粗,如果需要更大更粗的內陰影,可以使用一個負數的 Margin 配合同樣粗細的 BorderThickness 實現。以 OpacityMask 的方案為例,用下面的程式碼可以做個又粗又大的內陰影:
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
ShadowElement.Margin = new Thickness(-e.NewValue);
ShadowElement.BorderThickness = new Thickness(e.NewValue);
(ShadowElement.Effect as DropShadowEffect).BlurRadius = e.NewValue * 2;
}

1.8 用 OpacityMask 模仿 UWP 的 Text Shimmer 動畫

在 UWP 的 Windows Composition Samples 中有一個 Text Shimmer 動畫,它用於展示如何使用 Composition Light。這個動畫很簡單,就是用 PointLight 從左到右掃過一行文字。雖然 WPF 沒有 Composition Light,但要玩這個簡單的動畫仍然沒問題,就是用 OpacityMask 模仿一下而已。
RadialGradientBrush 代表一個圓形的漸層畫刷,在這裡我們要關心它的三個屬性:
RadiusX/RadiusY: 圓形的水平/垂直半徑。 Center: 圓形的最外圍的中心。 GradientOrigin: 漸層開始的二維焦點的位置。
這三個屬性的作用可以參考下圖:

用一個 RadialGradientBrush 作為 OpacityMask 讓 TextBlock 從中心點向外漸漸變得透明:
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontFamily="SegoeUI"
FontSize="100"
FontWeight="Thin"
Foreground="DimGray"
Text="Text Shimmer">
<TextBlock.OpacityMask>
<RadialGradientBrush x:Name="Brush" Center=".5,.5" GradientOrigin=".5,.5" RadiusX=".43" RadiusY="2">
<GradientStop Color="Black" />
<GradientStop Offset=".5" Color="#6000" />
<GradientStop Offset="1" Color="#2000" />
</RadialGradientBrush>
</TextBlock.OpacityMask>
</TextBlock>
然後對 Center 和 GradientOrigin 做 PointAnimation,實現 OpacityMask 的水平移動,就可以模仿出 PointLight 掃過的效果:
<PointAnimation RepeatBehavior="Forever"
Storyboard.TargetName="Brush"
Storyboard.TargetProperty="Center"
From="-2,.5"
To="3,.5"
Duration="0:0:3.3" />
<PointAnimation RepeatBehavior="Forever"
Storyboard.TargetName="Brush"
Storyboard.TargetProperty="GradientOrigin"
From="-2,.5"
To="3,.5"
Duration="0:0:3.3" />
1.9 抄一個 CSS3 實現的按鈕

抄一個 CSS3 實現的按鈕,順便熟悉一下 CSS3。
1.10 用 Effect 實現線條光影效果

為了實現這個效果我用到這些知識和技巧:
- Segoe Fluent 圖示字型
- 在 Blend 中建立 Path
- 計算 Path 的長度
- Path 的邊框動畫
- VisualStudio 的設計時資料支援
- 自訂 Effect
2. License
The project is released under MIT License.
3. UWP 的版本
另外,我有另一個用於玩 UWP 動畫的專案:
https://github.com/DinoChan/uwp_design_and_animation_lab

轉載自 GitHub
作者:dino.c
倉庫地址:https://github.com/DinoChan/wpf_design_and_animation_lab