上一篇文章介绍了使用Resizer实现Expander简单的动画效果,运行效果也还好,不过只有展开/折叠而缺少了淡入/淡出的动画(毕竟Resizer模仿Expander只是附带的功能)。这篇继续Measure的话题,自定义了一个带有动画的ExtendedExpander。
2. ExtendedExpander的需求使用Resizer实现的简易Expander没办法在折叠时做淡出动画,因为ControlTemplate中的ExpandSite在Collapsed状态下直接设置为隐藏。一个稍微好看些的Expander的状态改变动画要满足下面的需求:
拉伸
淡入淡出
上面两个效果都可以用XAML定义
最终运行效果如下:
3. 实现思路模仿SilverlightToolkit,我也用一个带有Percentage属性的ExpandableContentControl控件控制Expander内容的拉伸。(顺便一提,SilverlightToolkit的Expander没有拉伸动画,ExpandableContentControl用在AccordionItem里面)。ExpandableContentControl的Percentage属性控制这个控件的展开的百分比,1为完全展开,0为完全折叠。
在ControlTemplate中使用VisualState控制Expanded/Collapsed的动画。VusialState.Storyboard控制VisualState的最终值,过渡动画由VisualStateGroup.Transitions控制,这在以前的 这篇文章 中有介绍过:
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" CornerRadius="3" SnapsToDevicePixels="true"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="ExpansionStates"> <VisualStateGroup.Transitions> <VisualTransition GeneratedDuration="0:0:0.3"> <VisualTransition.GeneratedEasingFunction> <QuarticEase EasingMode="EaseOut"/> </VisualTransition.GeneratedEasingFunction> </VisualTransition> </VisualStateGroup.Transitions> <VisualState x:Name="Expanded"/> <VisualState x:Name="Collapsed"> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="ExpandableContentControl"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="Percentage" Storyboard.TargetName="ExpandableContentControl"> <EasingDoubleKeyFrame KeyTime="0" Value="0"/> </DoubleAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <DockPanel> <ToggleButton x:Name="HeaderSite" ContentTemplate="{TemplateBinding HeaderTemplate}" ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" Content="{TemplateBinding Header}" DockPanel.Dock="Top" Foreground="{TemplateBinding Foreground}" FontWeight="{TemplateBinding FontWeight}" FocusVisualStyle="{StaticResource ExpanderHeaderFocusVisual}" FontStyle="{TemplateBinding FontStyle}" FontStretch="{TemplateBinding FontStretch}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" MinWidth="0" MinHeight="0" Padding="{TemplateBinding Padding}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/> <Primitives:ExpandableContentControl x:Name="ExpandableContentControl" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}" ClipToBounds="True"> <ContentPresenter x:Name="ExpandSite" DockPanel.Dock="Bottom" Focusable="false" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Primitives:ExpandableContentControl> </DockPanel> </Border> <ControlTemplate.Triggers> <Trigger Property="IsExpanded" Value="false"> <Setter Property="IsHitTestVisible" TargetName="ExpandableContentControl" Value="False"/> </Trigger> ... </ControlTemplate.Triggers>这样Expander及它的ControlTemplate只做了最少的改动就实现了动画效果。主要的代码逻辑都交给ExpandableContentControl。
4. 实现ExpandableContentControlExpandableContentControl派生自ContentControl,它的Percentage属性的定义如下:
public static readonly DependencyProperty PercentageProperty = DependencyProperty.Register(nameof(Percentage), typeof(double), typeof(ExpandableContentControl), new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsMeasure));FrameworkPropertyMetadataOptions用于定义依赖属性的行为,其中AffectsMeasure的意思是依赖属性的值改变时要求重新Measure,既然Measure了Arrange也会发生,所以这个AffectsMeasure其实就是要求重新执行两步布局。功能和上一篇文章介绍的InvalidateMeasure差不多。