UWP Button新增圓角陰影(一)
眾所周知,17763之前的UWP控制元件,大部分是沒有圓角屬性的;而陰影也只有17763中的ThemeShadow可以直接在xaml中使用,之前的版本只能用DropShadow,用法極其彆扭。 本文就給出一個雖然很彆扭,但是效果還不錯的,比較通用的圓角+陰影的方案。
概念
我們先思考一下,使用者感知到的圓角按鈕,到底是個什麼東西。 任何一個按鈕,不外乎Background和Content兩部分,使用者可以從Content中獲取到按鈕的資訊,而按鈕的形狀,在沒有Border的情況下,使用者對Button形狀最直觀的感受就是Background! 也就是說,我們只要讓一個按鈕的Background是一個圓角矩形,他在大多數情況下,就是一個圓角按鈕! 按照這個思路,我們可以在17763之前的UWP,也就是大多數控制元件都沒有CornerRadius這個屬性的環境裡,造出許多圓角的控制元件。
圓角
從Generic.xaml中,把Button的Style複製出一份,刪除沒有必要的東西,就成了下面的樣子:
<Style TargetType="Button"> <Setter Property="Background" Value="{ThemeResource ButtonBackground}" /> <Setter Property="Foreground" Value="{ThemeResource ButtonForeground}" /> <Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderBrush}" /> <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" /> <Setter Property="Padding" Value="8,4,8,4" /> <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" Background="{TemplateBinding Background}"> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal" /> <VisualState x:Name="PointerOver"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPointerOver}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushPointerOver}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundPointerOver}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPressed}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushPressed}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundPressed}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> <VisualState x:Name="Disabled"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RootGrid" Storyboard.TargetProperty="Background"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundDisabled}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="BorderBrush"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBorderBrushDisabled}" /> </ObjectAnimationUsingKeyFrames> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentPresenter" Storyboard.TargetProperty="Foreground"> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonForegroundDisabled}" /> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <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>
其實大部分內容不重要,我們先來分析一下這個結構,刪掉所有佈局之外的屬性,就剩下很單純的Grid裝著一個ContentPresenter,是這樣的:
<Style TargetType="Button" x:Key="CornerRadiusShadowButtonStyle"> <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 x:Name="Pressed"/> <VisualState x:Name="Disabled"/> </VisualStateGroup> </VisualStateManager.VisualStateGroups> <ContentPresenter x:Name="ContentPresenter"/> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style>
圓角的屬性呢,只有在Border,Rectangle中有,當然你要是足夠閒,也可以擼Path... 結果很明顯了對吧,這就是圓角的兩個實現方式,ContentPresenter外面套Border,或者後面放Rectangle當Background。 Border的方式呢,優缺點都很明顯。 優點是Border的CornerRadius可以分別設定四個角的半徑,而且可以設定給Button設定Border相關屬性的時候,讓Border的相關屬性按照我們定義的Border的形狀去繪製。 缺點呢,就是在PC端,Border設定CornerRadius後,會Clip掉內容超出Border的部分(由於17763之前的UWP沒有GeometryClip,所以這個圓角Clip也算是個特性...),其實我們可以利用這個特性做圓形頭像,圓形播放器啥的... 而Rectangle呢,雖然不能分別設定四個角的半徑,但是可以分別設定X的半徑和Y的半徑...說起來感覺好詭異... 出個人喜好,還有我不喜歡畫Border的風格,就選Rectangle的解決方案了。修改完成之後的Style結構應該是這個模樣的:
<Style TargetType="Button" x:Key="CornerRadiusShadowButtonStyle">
<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 x:Name="Pressed"/>
<VisualState x:Name="Disabled"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="Background" />
<ContentPresenter x:Name="ContentPresenter"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
注意事項:VisualStateManager必須放到控制元件根元素內,比如Page的VisualStateManager就必須放到Page內第一個元素比如Grid或者StackPanel裡,不然是不會生效的。
完整版是這樣的,有改顏色的需求可以擼一下VisualState裡的顏色:
<Style TargetType="Button" x:Key="CornerRadiusShadowButtonStyle">
<Setter Property="Background" Value="{ThemeResource ButtonBackground}"/>
<Setter Property="Foreground" Value="{ThemeResource ButtonForeground}"/>
<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">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPointerOver}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Background" Storyboard.TargetProperty="Fill">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ButtonBackgroundPressed}" />
</ObjectAnimationUsingKeyFrames>
</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>
<Rectangle x:Name="Background" Fill="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" RadiusX="5" RadiusY="5" />
<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>