原文:UWP Button添加圓角陰影(三)css
DropShadow是CompositionAPI中的東西,使用Storyboard設置某個屬性,就是頻繁的觸發put_xxx()方法,效率遠遠不如使用CompositionAnimation。
Composition對象的基類CompositionObject擁有一個屬性叫ImplicitAnimations,能夠經過他實現累死css的transition的效果,也就是對應屬性修改的時候,平滑的過渡過去。
能夠從DropShadowPanel的源代碼中看到,DropShadow是設置在ShadowElement上的ChildVisual。
相關內容能夠查閱將可視化層與 XAML 結合使用 - ElementCompositionPreview.SetElementChildVisual 方法。
而咱們要作的,是把整個構造過程倒過來,經過VisualTreeHelper,從DropShadow中拿到ShadowElement,而後獲取他的ChildVisual和Shadow,將ImplicitAnimations設置到Shadow上。
下面貼代碼:html
public static class DropShadowPanelHelper { public static bool GetIsTransitionEnable(DropShadowPanel obj) { return (bool)obj.GetValue(IsTransitionEnableProperty); } public static void SetIsTransitionEnable(DropShadowPanel obj, bool value) { obj.SetValue(IsTransitionEnableProperty, value); } public static readonly DependencyProperty IsTransitionEnableProperty = DependencyProperty.RegisterAttached("IsTransitionEnable", typeof(bool), typeof(DropShadowPanelHelper), new PropertyMetadata(false, IsTransitionEnablePropertyChanged)); private static void IsTransitionEnablePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (e.NewValue != e.OldValue) { if (d is DropShadowPanel sender) { //嘗試獲取ShadowElement,若是爲空,多是DropShadowPanel尚未ApplyTemplate,註冊DropShadowPanel的Loaded事件,在Loaded中再獲取一次。 var shadowElement = sender.FindDescendantByName("ShadowElement") as Border; if (shadowElement != null) { SetImplicitAnimation(shadowElement, (bool)e.NewValue); } else { sender.Loaded += DropShadowPanel_Loaded; } } } } private static void DropShadowPanel_Loaded(object sender, RoutedEventArgs e) { var dropShadowPanel = (DropShadowPanel)sender; dropShadowPanel.Loaded -= DropShadowPanel_Loaded; var shadowElement = dropShadowPanel.FindDescendantByName("ShadowElement") as Border; if (shadowElement != null) { SetImplicitAnimation(shadowElement, GetIsTransitionEnable(dropShadowPanel)); } } private static void SetImplicitAnimation(FrameworkElement element, bool IsEnable) { if (ElementCompositionPreview.GetElementChildVisual(element) is SpriteVisual shadowVisual && shadowVisual.Shadow is DropShadow shadow) { if (IsEnable) { //獲取合成器 var compositor = shadowVisual.Compositor; //建立ImplicitAnimationCollection var imp = compositor.CreateImplicitAnimationCollection(); //建立BlurRadius動畫,注意不要忘記設置Duration和Target var bluran = compositor.CreateScalarKeyFrameAnimation(); //插入一個表達式關鍵幀,幀在進度爲1的時候,值是最終值 bluran.InsertExpressionKeyFrame(1f, "this.FinalValue"); bluran.Duration = TimeSpan.FromSeconds(0.2d); bluran.Target = "BlurRadius"; //建立Offset動畫 var offsetan = compositor.CreateVector3KeyFrameAnimation(); offsetan.InsertExpressionKeyFrame(1f, "this.FinalValue"); offsetan.Duration = TimeSpan.FromSeconds(0.2d); offsetan.Target = "Offset"; //建立Opacity動畫 var opacityan = compositor.CreateScalarKeyFrameAnimation(); opacityan.InsertExpressionKeyFrame(1f, "this.FinalValue"); opacityan.Duration = TimeSpan.FromSeconds(0.2d); opacityan.Target = "Opacity"; //ImplicitAnimationCollection是IDictionary,每一個子項都要是KeyFrame動畫,子項的Key和動畫的Target要同樣。 ImplictAnimationCollection imp[bluran.Target] = bluran; imp[offsetan.Target] = offsetan; imp[opacityan.Target] = opacityan; //給shadow設置ImplicitAnimations shadow.ImplicitAnimations = imp; } else { var imp = shadow.ImplicitAnimations; shadow.ImplicitAnimations = null; if (imp != null) { imp.Dispose(); imp = null; } } } } }
表達式關鍵幀的關鍵字相關的內容能夠查閱:ExpressionAnimation Class - Expression Keywords
最後的Xaml是這樣的:express
<Style TargetType="Button" x:Key="CornerRadiusShadowButtonStyle"> <Setter Property="Background" Value="#007acc" /> <Setter Property="Foreground" Value="White" /> <Setter Property="BorderBrush" Value="Transparent" /> <Setter Property="BorderThickness" Value="0" /> <Setter Property="Padding" Value="20,10,20,10" /> <Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" /> <Setter Property="FontWeight" Value="Normal" /> <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" /> <Setter Property="UseSystemFocusVisuals" Value="True" /> <Setter Property="FocusVisualMargin" Value="-3" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="Button"> <Grid x:Name="RootGrid"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="PointerOver"> <VisualState.Setters> <Setter Target="Shadow.OffsetY" Value="2" /> <Setter Target="Shadow.BlurRadius" Value="8" /> </VisualState.Setters> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <DoubleAnimation Storyboard.TargetName="Shadow" Storyboard.TargetProperty="OffsetY" To="3" Duration="0" /> <DoubleAnimation Storyboard.TargetName="Shadow" Storyboard.TargetProperty="BlurRadius" To="12" Duration="0" /> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background" Storyboard.TargetProperty="Fill"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundDisabled}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <control:DropShadowPanel x:Name="Shadow" xmlns:control="using:Microsoft.Toolkit.Uwp.UI.Controls" xmlns:helper="using:TestApp1.Helpers" HorizontalContentAlignment="Stretch" helper:DropShadowPanelHelper.IsTransitionEnable="True" BlurRadius="5" OffsetX="1" OffsetY="1" Color="Black"> <Rectangle x:Name="Background" Fill="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RadiusX="5" RadiusY="5" /> </control:DropShadowPanel> <ContentPresenter x:Name="ContentPresenter" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Content="{TemplateBinding Content}" ContentTransitions="{TemplateBinding ContentTransitions}" ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" AutomationProperties.AccessibilityView="Raw" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
啓用方式就是
xmlns:helper="using:TestApp1.Helpers" helper:DropShadowPanelHelper.IsTransitionEnable="True"
VisualState中有兩種寫法,第一種是寫在Setter中,優勢是寫的少,缺點是不能控制起始時間。
第二種是寫在Storyboard中,Duration必定要是0,由於真正的動畫咱們定義在ImplicitAnimation中了,這裏的Animation只是用來觸發值修改,不須要插值。
最終效果有點小掉幀,是DropShadow的問題,17763中的ThemeShadow應該會有改善。。。
windows