[WPF] 玩玩彩虹文字及動畫
1. 前言
興緻來了玩玩 WPF 的彩虹文字。不是用 LinearGradientBrush 製作漸變色那種,是指每個文字獨立顏色那種彩虹文字。雖然沒什麼實用價值,但希望這篇文章里用 ItemsControl 拆分文字,以及用工具類提供遞增和隨機變量的做法可以給讀者一些啟發,就好了。
2. 用 TextBlock 的 Run
<TextBlock>
<Run Foreground="#4a0e68">b</Run>
<Run Foreground="#b62223">l</Run>
<Run Foreground="#fdd70c">o</Run>
<Run Foreground="#f16704">c</Run>
<Run Foreground="#69982d">k</Run>
<Run Foreground="#0075a5">.</Run>
<Run Foreground="#0b0045">R</Run>
<Run Foreground="#4a0e68">u</Run>
<Run Foreground="#b62223">n</Run>
</TextBlock>
用 TextBlock 的 Run 是做基本的做法,還有其它各種給 TextBlock 設置格式的方法,具體可以參考 text-block#formatting-text 這篇文檔。
3. 寫代碼
這種方案就是用代碼將字符串拆分,然後逐個字符塞進 TextBlock 然後放進 StackPanel,實現方式很無趣,我就不寫了。
4. 用 ItemsControl
第三種方案是用 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}" />
5. 動畫
從第三種方案延申,我試玩了幾種動畫。
首先我寫了個 TimeSpanIncreaser 類,它包含 Start、Setp、Next 三個屬性,其中 Next 的代碼如下:
public override TimeSpan Next => Start + (_current += Step);
它的作用就是每次調用 Next 屬性,這個屬性返回的值都遞增。
回到 Xaml,首先在 Resources 中定義一個實例:
<common:TimeSpanIncreaser x:Key="TimeSpanIncreaser" Step="0:0:0.2" />
然後在 TextBlock 里加上這段:
<TextBlock.RenderTransform>
<TranslateTransform Y="-90" />
</TextBlock.RenderTransform>
<FrameworkElement.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation BeginTime="{Binding Next, Source={StaticResource TimeSpanIncreaser}}"
Storyboard.TargetName="TextElement"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"
To="0"
Duration="0:0:0.7">
<DoubleAnimation.EasingFunction>
<BounceEase EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</FrameworkElement.Triggers>
每個 TextBlock 使用相同的動畫,但動畫的開始時間是逐個遞增的,運行起來效果如下:
再大膽些,ItemsControl 嵌套 ItemsControl,就可以做出下面這種效果:
又或者,這次不玩遞增,玩隨機。首先實現這兩個類然後實例化,代碼我就不貼出來了,看名字就能懂它們實現了什麼功能:
<common:RandomColorCreator x:Key="RandomColorCreator" />
<common:RandomDoubleCreator x:Key="RandomDoubleCreator" Max="20" />
然後讓 TextBlock 的 Foreground 和 TranslateTransform 動畫的 X、Y 綁定到這兩個實例的 Next 屬性:
<TextBlock.Foreground>
<SolidColorBrush Color="{Binding Next, Source={StaticResource RandomColorCreator}}" />
</TextBlock.Foreground>
<TextBlock.RenderTransform>
<TranslateTransform />
</TextBlock.RenderTransform>
<FrameworkElement.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation AutoReverse="True"
RepeatBehavior="Forever"
Storyboard.TargetName="TextElement"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
To="{Binding Next, Source={StaticResource RandomDoubleCreator}}"
Duration="0:0:.08" />
<DoubleAnimation AutoReverse="True"
RepeatBehavior="Forever"
Storyboard.TargetName="TextElement"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)"
To="{Binding Next, Source={StaticResource RandomDoubleCreator}}"
Duration="0:0:.1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</FrameworkElement.Triggers>
又一個毫無實用價值的動畫誕生了:
6. 最後
雖然很遺憾沒什麼用,我只能安慰自己「結果不重要,最重要是享受過程」。