原文: "UWP Button添加圓角陰影(三)" Composition DropShadow是CompositionAPI中的東西,使用Storyboard設置某個屬性,就是頻繁的觸發put_xxx()方法,效率遠遠不如使用CompositionAnimation。 Composition對象的 ...
Composition
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上。
下麵貼代碼:
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是這樣的:
<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>
啟用方式就是