原文: WPF實作QQ群檔案清單動畫(一) QQ群大家都用過,先看下目前QQ的群檔案清單容器的效果:
細心點大家就會發現,這玩意收縮和展開是帶動畫的,并不是很僵硬地直接收縮或者直接展開,毫無疑問,如果用WPF實作這樣的效果,這裡的最佳控件是Expander,WPF的Expander控件自帶Collapse和Expand功能,但是用過Expander的人都知道,這玩意的Collapse或者Expand是瞬間完成的,找遍Expander所有的屬性,沒有發現能設定為動畫伸縮的,于是想到它裡面一探究竟。用Blend編輯樣式如下圖:通過Blend可以清楚地看到Expand的時候發生了什麼,當它Expand的時候,把ExpandSite的Visibility改成了Visible,ExpandSite是什麼呢,它就是Expander的ContentPresenter,即内容載體,難怪呢,Visibility是瞬間的,沒有什麼動畫可言。
看到這也許你會很失望,因為沒法對Visibility這個屬性做動畫。是不是真的沒有辦法了呢,當然不是,沒有條件,可以創造條件。想想能做動畫的是什麼,最直接的,高度,或者——Transform,那麼,即使我能對單個Expander做動畫伸縮,怎麼保證其他的Expander能動畫上下位移呢?現在有3個問題需要解決:
1.如何去掉Expander本身的Expander和Collapse效果,因為自帶的效果是單純的設定Visibility,這個屬性是沒法做動畫的
2.如何對單個Expander做動畫伸縮,也就是使用它的哪個屬性做動畫
3.對某個Expander伸縮的時候如何讓其他的Expander自動位移
這三個問題解決了,那麼這種效果就實作了。
問題1的解決思路
似乎這三個問題都涉及到了控件内部的一些邏輯,最直接的想法是寫個類繼承Expander,然後去override相關函數,我試過,沒什麼作用,即使不用調base的函數,該出現的還是會出現。如果我有源碼,或者我會在Measure裡做些什麼,也許可以改動一些邏輯,可惜我不會,我隻會改改樣式什麼的。于是我想到了繼承和樣式相結合——事實上這種辦法很大程度上簡化了控件的開發(相對于遊離在VisualTree和LogicTree之間的程式員來說),因為Style能快速增減控件,但是實作的邏輯有限,而繼承控件能輕易實作邏輯,但對于一些人來說,繼承後再加個控件,在哪裡加,位置、背景、Margin、BorderThickness如何這些都太TM難了,調試難度也不低,是以繼承和樣式結合,各取所優,利益最大化。這樣的話,我可以寫個樣式,把IsExpanded觸發的邏輯去掉,然後寫個類繼承Expander,在構造函數裡找到這個樣式并設定為自己的Style,那麼第一個問題就解決了。
問題2的解決思路
我想選高度來做動畫吧,收縮好辦,變為0就可以了,展開呢,高度該變為多少呢,Expander的高度是Auto,也就是根據内容來的,内容有多高展開就有多高,DoubleAnimation的To隻是一個Double,沒法綁定,這條路似乎有點難度。那麼我用變形效果做動畫呢,收縮的時候Y軸縮放為0,展開的時候Y軸縮放為1,這樣我根本不用關心Expander的高度具體是多少,這樣一來,問題2也得到了解決。
問題3的解決思路
單個的Expander行為怎麼去影響别人的行為呢,這似乎有點為難。其實也不難,選好容器就可以,你把它們放在Grid裡肯定是不行的,Gird隻提供行列和Margin,如果要我關聯Expander的伸縮事件然後挨個去設行列或者margin,那是會死人的。很顯然,StackPanel最适合不過了,StackPanel提供Children了自動占用空間的特性,當一個child的高度變小,其他控件是會跟着移動的。但是還有個問題,我是選Expander的變形來做動畫,印象中控件的變形效果其實不是發生了真正的布局改變,是以還有一點要注意,就是使用LayoutTransform,這個變形效果是會影響到布局的,而這正是我想要的結果,這樣一來,問題3也解決了。以下是效果圖:
以下是部分代碼:
1 <Setter Property="Template">
2 <Setter.Value>
3 <ControlTemplate TargetType="{x:Type Expander}">
4 <ControlTemplate.Resources>
5 <Storyboard x:Key="STHide">
6 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
7 Storyboard.TargetName="ExpandSite">
8 <EasingDoubleKeyFrame KeyTime="0:0:0.2"
9 Value="0" />
10 </DoubleAnimationUsingKeyFrames>
11 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
12 Storyboard.TargetName="ExpandSite">
13 <EasingDoubleKeyFrame KeyTime="0:0:0.2"
14 Value="1" />
15 </DoubleAnimationUsingKeyFrames>
16 </Storyboard>
17 <Storyboard x:Key="STShow">
18 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"
19 Storyboard.TargetName="ExpandSite">
20 <EasingDoubleKeyFrame KeyTime="0"
21 Value="0" />
22 <EasingDoubleKeyFrame KeyTime="0:0:0.2"
23 Value="1" />
24 </DoubleAnimationUsingKeyFrames>
25 <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)"
26 Storyboard.TargetName="ExpandSite">
27 <EasingDoubleKeyFrame KeyTime="0"
28 Value="0" />
29 <EasingDoubleKeyFrame KeyTime="0:0:0.2"
30 Value="1" />
31 </DoubleAnimationUsingKeyFrames>
32 </Storyboard>
33 </ControlTemplate.Resources>
34 <Border BorderBrush="{TemplateBinding BorderBrush}"
35 BorderThickness="{TemplateBinding BorderThickness}"
36 Background="{TemplateBinding Background}"
37 CornerRadius="3"
38 SnapsToDevicePixels="true">
39 <DockPanel>
40 <ToggleButton x:Name="HeaderSite"
41 ContentTemplate="{TemplateBinding HeaderTemplate}"
42 ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}"
43 Content="{TemplateBinding Header}"
44 DockPanel.Dock="Top"
45 Foreground="{TemplateBinding Foreground}"
46 FontWeight="{TemplateBinding FontWeight}"
47 FocusVisualStyle="{StaticResource ExpanderHeaderFocusVisual}"
48 FontStyle="{TemplateBinding FontStyle}"
49 FontStretch="{TemplateBinding FontStretch}"
50 FontSize="{TemplateBinding FontSize}"
51 FontFamily="{TemplateBinding FontFamily}"
52 HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
53 IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
54 Margin="1"
55 MinWidth="0"
56 MinHeight="0"
57 Padding="{TemplateBinding Padding}"
58 Style="{StaticResource ExpanderDownHeaderStyle}"
59 VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
60 <ContentPresenter x:Name="ExpandSite"
61 DockPanel.Dock="Bottom"
62 Focusable="false"
63 HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
64 Margin="{TemplateBinding Padding}"
65 Visibility="Visible"
66 VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
67 <ContentPresenter.LayoutTransform>
68 <TransformGroup>
69 <ScaleTransform />
70 <SkewTransform />
71 <RotateTransform />
72 <TranslateTransform />
73 </TransformGroup>
74 </ContentPresenter.LayoutTransform>
75 </ContentPresenter>
76 </DockPanel>
77 </Border>
78 <ControlTemplate.Triggers>
79 <EventTrigger RoutedEvent="FrameworkElement.Loaded">
80 <BeginStoryboard Storyboard="{StaticResource STHide}" />
81 </EventTrigger>
82 <EventTrigger RoutedEvent="Expander.Expanded">
83 <BeginStoryboard x:Name="STShow_BeginStoryboard"
84 Storyboard="{StaticResource STShow}" />
85 </EventTrigger>
86 <EventTrigger RoutedEvent="Expander.Collapsed">
87 <BeginStoryboard Storyboard="{StaticResource STHide}" />
88 </EventTrigger>
89 <Trigger Property="ExpandDirection"
90 Value="Right">
91 <Setter Property="DockPanel.Dock"
92 TargetName="ExpandSite"
93 Value="Right" />
94 <Setter Property="DockPanel.Dock"
95 TargetName="HeaderSite"
96 Value="Left" />
97 <Setter Property="Style"
98 TargetName="HeaderSite"
99 Value="{StaticResource ExpanderRightHeaderStyle}" />
100 </Trigger>
101 <Trigger Property="ExpandDirection"
102 Value="Up">
103 <Setter Property="DockPanel.Dock"
104 TargetName="ExpandSite"
105 Value="Top" />
106 <Setter Property="DockPanel.Dock"
107 TargetName="HeaderSite"
108 Value="Bottom" />
109 <Setter Property="Style"
110 TargetName="HeaderSite"
111 Value="{StaticResource ExpanderUpHeaderStyle}" />
112 </Trigger>
113 <Trigger Property="ExpandDirection"
114 Value="Left">
115 <Setter Property="DockPanel.Dock"
116 TargetName="ExpandSite"
117 Value="Left" />
118 <Setter Property="DockPanel.Dock"
119 TargetName="HeaderSite"
120 Value="Right" />
121 <Setter Property="Style"
122 TargetName="HeaderSite"
123 Value="{StaticResource ExpanderLeftHeaderStyle}" />
124 </Trigger>
125 <Trigger Property="IsEnabled"
126 Value="false">
127 <Setter Property="Foreground"
128 Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
129 </Trigger>
130 </ControlTemplate.Triggers>
131 </ControlTemplate>
132 </Setter.Value>
133 </Setter>
View Code
這個樣式主要是用過LayoutTransform下的ScaleTransForm做了伸縮動畫,然後關聯Expanded和Collapsed事件執行動畫。
1 <EventTrigger RoutedEvent="Expander.Expanded">
2 <BeginStoryboard x:Name="STShow_BeginStoryboard"
3 Storyboard="{StaticResource STShow}" />
4 </EventTrigger>
5 <EventTrigger RoutedEvent="Expander.Collapsed">
6 <BeginStoryboard Storyboard="{StaticResource STHide}" />
7 </EventTrigger>
到這裡似乎不需要再寫個繼承類來實作什麼邏輯了,的确,這樣的功能一個樣式就搞定了,不過這裡面有個缺陷,至于是什麼缺陷,有什麼辦法彌補,将在下一篇闡述,敬請期待。
本篇源碼已在QQ群裡共享,如有需要可以下載下傳來研究。