• UWP Button添加圆角阴影(三)


    原文:UWP Button添加圆角阴影(三)

    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>
    

    启用方式就是中的xmlns:helper="using:TestApp1.Helpers" helper:DropShadowPanelHelper.IsTransitionEnable="True"
    VisualState中有两种写法,第一种是写在Setter中,优点是写的少,缺点是不能控制起始时间。
    第二种是写在Storyboard中,Duration一定要是0,因为真正的动画我们定义在ImplicitAnimation中了,这里的Animation只是用来触发值修改,不需要插值。
    最终效果有点小掉帧,是DropShadow的问题,17763中的ThemeShadow应该会有改善。。。

  • 相关阅读:
    Linux小命了(6)cd
    Linux小命令(5)mkdir
    性能测试(1)-开篇杂谈
    Linux小命令(4)ls
    Linux小命令(3)cat
    Linux小命令(2)man
    机器学习-学习方法
    flask-cache
    mysql 常用时间函数
    发现变化,拥抱变化
  • 原文地址:https://www.cnblogs.com/blue-fire/p/9768780.html
Copyright © 2020-2023  润新知