[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. 最後

雖然很遺憾沒什麼用,我只能安慰自己“結果不重要,最重要是享受過程”。

7. 源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章