有幾十個WPF設計和動畫的專案

有幾十個WPF設計和動畫的專案

這是一個 WPF 專案,用於創作及收集一些好玩的設計和動畫。目前已有數十個 Demo,部分 Demo 有相關部落格介紹詳細的實作步驟和原理

最後更新 2022/4/17 下午5:51
dino.c
預計閱讀 9 分鐘
分類
WPF
專題
WPF UI設計
標籤
.NET WPF WPF UI設計 動畫 設計

Wpf Design And Animation Lab

這是一個 WPF 專案,用於創作及收集一些好玩的設計和動畫。目前已有數十個 Demo,部分 Demo 有相關部落格介紹詳細的實作步驟和原理:

設計和動畫 - dino.c - 部落格園

透過這些部落格,你將會了解到如何實現一些酷酷的 WPF 動畫和設計,以及一些 WPF 的技術細節。

1. 已實現的設計和動畫

1.1 使用三種方式實現弧形進度條

image

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

1.2 僅用 Rectangle 實現圓柱形進度條

image

圓柱形進度條不難實現,不過有趣的是上圖完全由代表矩形的 Rectangle 組成,這稍微有點反直覺。

首先我們需要重溫一些基礎知識:Rectangle 顯示帶圓角的矩形。用 RadiusXRadiusY 可分別指定用於使矩形的角變圓的橢圓的 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" />

image

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

image

<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}" />

image

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

image

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>

image

在 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 度,所以實際上是向上下兩個方向推:

image

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

image

image

用拆分文字和 TimeSpanIncreaser 的方案,實現了兩個在任天堂 Switch 中最常見的動畫。

1.6 使用 Shazzam Shader Editor 編寫一個 Lighten Effect

在上面的動畫裡為了實現不同亮度的 Grid,使用了一個 LightenConverter 類別,但是它只能處理 SolidColorBrush。為了可以應用在更多場合,接下來自己寫一個 Effect 來實現相同 Lighten 的效果。

image

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;
}

image

1.8 用 OpacityMask 模仿 UWP 的 Text Shimmer 動畫

image

在 UWP 的 Windows Composition Samples 中有一個 Text Shimmer 動畫,它用於展示如何使用 Composition Light。這個動畫很簡單,就是用 PointLight 從左到右掃過一行文字。雖然 WPF 沒有 Composition Light,但要玩這個簡單的動畫仍然沒問題,就是用 OpacityMask 模仿一下而已。

RadialGradientBrush 代表一個圓形的漸層畫刷,在這裡我們要關心它的三個屬性:

RadiusX/RadiusY: 圓形的水平/垂直半徑。 Center: 圓形的最外圍的中心。 GradientOrigin: 漸層開始的二維焦點的位置。

這三個屬性的作用可以參考下圖:

image

用一個 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 實現的按鈕

image

抄一個 CSS3 實現的按鈕,順便熟悉一下 CSS3。

1.10 用 Effect 實現線條光影效果

image

為了實現這個效果我用到這些知識和技巧:

  • 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

image

轉載自 GitHub

作者:dino.c

倉庫地址:https://github.com/DinoChan/wpf_design_and_animation_lab

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2025/1/26

WPF 藉助自訂 XML 檔案實現國際化

本文詳細介紹了在WPF程式中使用自訂XML檔案實現國際化的方法,包括安裝必備NuGet套件、動態獲取語言清單、動態切換語言、在程式碼和XAML介面中使用翻譯字串等內容,同時提供了原始碼連結,幫助開發者輕鬆實現WPF應用程式的國際化。

繼續閱讀