UWP WinUI 制作一个路径矢量图标按钮样式入门
本文将告诉大家如何在 UWP 或 WinUI3 或 UNO 里,如何制作一个路径按钮。路径按钮就是使用几何路径轮廓表示内容的按钮,常见于各种图标按钮,或 svg 系贴图矢量图按钮
在网上有非常多矢量图库,其中免费的图库也非常多,比如 https://www.iconfont.cn/ 等等。在咱的应用程序里面,可以使用这些矢量图作为按钮的图标,从而更好的进行表意,让界面有更好的设计。使用矢量图还自然带有缩放时依然清晰的功能
最为简单的制作方式就是在按钮里面存放一个 Path 作为内容,比如做一个简单的路径矢量图标按钮
<Button HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Stroke="Black" StrokeThickness="1" Data="M5,5L15,15L5,25"></Path>
</Button>
可以看到简单的几行代码就可以实现一个图标按钮的功能了。不过哩作为有追求的开发者,可不能像在树上的小猫一样,咱还需要多加一些需求。比如我希望鼠标移动到按钮上的时候,按钮可以变色,比如说我感觉上面的重复代码多了,即我有多个图标按钮都有大量相似的代码,能不能做一个样式实现这些功能?当然是可以的啦
先在一个资源里面定义按钮的样式,资源可以放在自己的应用业务代码 xaml 文件里面,也可以单独做一个资源字典。本文为了简单,就放在 MainPage.xaml 里面了。如果大家想要放在资源字典里面,别忘了引用资源字典哦
<Style x:Key="Style.Button.PathButtonStyle" TargetType="Button">
...
</Style>
如上面代码,就定义了一个名为 Style.Button.PathButtonStyle
的代码样式。这样的样式命名方法是我习惯用的,因为如此可以方便一级级点下去,特别在有 ReSharper 的帮助下,会更加好用,在样式特别多的时候,这样写能够和 ReSharper 更好的进行配合
这样的样式,可以应用到按钮代码上,如下面代码
<Button Style="{StaticResource Style.Button.PathButtonStyle}"
.../>
此样式都是给路径图标按钮制作,可以制作非常明确的按钮样式实现。对于 xaml 的界面样式实现的编码思路有些会和 C# 不一样,即不追求抽象性,有很多界面逻辑都是越具体越好,且允许有一些代码是重复的。核心追求就是让界面代码在看的时候可以更好的和界面效果联系起来,按照界面组织的方式走而不是按照逻辑的组织方式走。且有些界面效果是追求界面像,而不追求逻辑合理,即只要界面像就好更重要,当然,能两者都兼顾那是最好的。放心,本文提供的方法还是两者都兼顾的。那是否只有本文介绍的附加属性的方法才是最佳实践?当然不是啦,只不过这个方法我用的顺手而已
<Style x:Key="Style.Button.PathButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
<Path x:Name="ButtonContentPath" StrokeThickness="1" Stroke="Black" Data="M5,5L15,15L5,25"></Path>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
如上面代码,即定义了一个图标按钮的样式,也写明了图标按钮的内容,应用此样式的按钮即可显示出也如上图的效果
样式自然是追求一定的通用性的,上面代码只能显示固定的路径图标,自然不符合咱的需求。能否让 ButtonContentPath 的 Data 参与业务定制?自然是可以的,接下来咱使用简单的附加属性来解决此问题
通过附加属性的方式,既可以用在 UWP 等框架上,同样在 WPF 里面也是可以使用的,毕竟都是相同系列的框架
在后台 cs 代码里面定义一个名为 ButtonHelper 的类型,将在这个类型里面定义附加属性,实现代码如下
public class ButtonHelper
{
public static readonly DependencyProperty ButtonPathProperty = DependencyProperty.RegisterAttached(
"ButtonPath", typeof(string), typeof(ButtonHelper), new PropertyMetadata(default(string)));
public static void SetButtonPath(DependencyObject element, string value)
{
element.SetValue(ButtonPathProperty, value);
}
public static string GetButtonPath(DependencyObject element)
{
return (string)element.GetValue(ButtonPathProperty);
}
... // 忽略其他代码
}
如此即可在样式里面进行绑定 Data 的内容,核心代码如下
<Path x:Name="ButtonContentPath" StrokeThickness="2" Stroke="#FF666666" Data="{Binding RelativeSource={RelativeSource TemplatedParent},Path=(local:ButtonHelper.ButtonPath)}"></Path>
如果大家看了本文的内容不知道代码写在哪,可以到本文末尾获取所有代码的下载方法,拉取我的代码跑跑看
通过以上代码,可以看到使用 (local:ButtonHelper.ButtonPath)
将 Data 绑定到 ButtonHelper 的 ButtonPath 附加属性上,属性源是通过 RelativeSource={RelativeSource TemplatedParent}
指定的,在这里就是按钮本身。以上代码的 local:
的 local 表示的 xaml 命名空间,这是因为我将 ButtonHelper 放在和 MainPage 相同的命名空间上,于是就刚好就是 local 的值,如果大家放在其他命名空间,还请在 VisualStudio 的帮助下进行命名空间引用 。以上代码的细节在于必须通过 RelativeSource
和 TemplatedParent 指定,且使用 Binding 进行绑定,不能通过 TemplateBinding 和 Source 指定绑定
应用以上样式的按钮,需要在按钮上给 ButtonHelper 的 ButtonPath 附加属性进行赋值,如以下代码
<Button Style="{StaticResource Style.Button.PathButtonStyle}"
local:ButtonHelper.ButtonPath="M5,5L15,15L5,25"
.../>
运行代码也可以看到大概如上图的效果,也就是本文以上提供的三个方式都是实现相同的一个按钮效果。可以看到第一个代码最简单,最后一个代码最有通用性,可以将更多的图标按钮使用样式减少重复的代码
那接下来给样式提出更多的要求,如鼠标移动到按钮上方时,修改按钮的图标颜色
对于 Path 元素来说,可以通过 Stroke 和 StrokeThickness 分别修改轮廓颜色画刷和轮廓线条粗细,可以使用 Fill 属性修改填充画刷。在鼠标移动到按钮上方,即 PointerOver 时,通过设置轮廓画刷或填充画刷即可修改按钮的图标颜色
期望在鼠标移动到按钮上方,即 PointerOver 时,设置轮廓画刷或填充画刷,需要配合 VisualStateManager 提供的多个视觉状态,在每个视觉状态下给属性赋值或制作动画
最常用的 VisualStateManager 的 VisualStateGroup 是 CommonStates 组,基础代码组成如下
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<!-- 正常状态 -->
</VisualState>
<VisualState x:Name="PointerOver">
<!-- 鼠标移动到控件上,即 WPF 的 Hover 效果 -->
</VisualState>
<VisualState x:Name="Pressed">
<!-- 鼠标按下的状态,或叫点击的状态 -->
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
这里有一些细节事项:
- 由于 VisualStateManager 必须放在容器里面,因此这里必须需要有一个容器控件住 VisualStateManager 的代码。简单的容器就是如 Grid 等控件,想省一点资源的话,可以用 Border 代替 Grid 做容器
- 一般情况下,第一个状态是 Normal 状态,里面啥都不用做,啥都不用做可以清空其他的 VisualState 的状态。正常状态需要放在第一个
开始编写正式的代码之前,先复习一下 VisualStateManager 的用法,如下面的代码,既可以在 VisualState 里面使用 Setter 修改属性。也可以使用 Storyboard 做动画修改属性。做动画的方式可以比较柔和,有渐变的效果
<Page.Resources>
<Style x:Key="Style.Button.FooButtonStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="RootGrid" Background="{TemplateBinding Background}">
<!-- 由于 VisualStateManager 必须放在容器里面,因此这里必须需要有一个容器 想省一点的话,可以用 Border 代替 Grid 做容器-->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<!--啥都不用做,清空状态即可-->
<!-- 正常状态需要放在第一个 -->
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<!-- Hover 效果 -->
<VisualState.Setters>
<Setter Target="ButtonContentPath.Stroke" Value="Transparent"></Setter>
<Setter Target="ButtonContentPath.Fill" Value="Blue"></Setter>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
<!-- 按下状态 -->
<Storyboard>
<!-- 颜色用 ColorAnimation 也可以 -->
<!-- <ColorAnimation To=""></ColorAnimation> -->
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonContentPath" Storyboard.TargetProperty="Stroke" >
<DiscreteObjectKeyFrame KeyTime="0" Value="Transparent"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonContentPath" Storyboard.TargetProperty="Fill" >
<DiscreteObjectKeyFrame KeyTime="0" Value="#FF666666"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<!-- 不可用的状态 -->
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path x:Name="ButtonContentPath" StrokeThickness="2" Stroke="#FF666666" Data="M7,15C6.85289858,15.5677816,6.85289858,16.4322348,7,17L22,29C22.7348015,29.3762198,24,28.8227297,24,28L24,4C24,3.1772867,22.7348015,2.62379657,22,3L7,15z"></Path>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border BorderBrush="Black" BorderThickness="0">
<Button Style="{StaticResource Style.Button.FooButtonStyle}"
Background="Transparent" Padding="0">
</Button>
</Border>
</StackPanel>
以上代码的细节点在于想要让 VisualStateManager 的 VisualStateGroups 生效,必须放在容器里面,直接写在 ControlTemplate 下面是不行的。第一个 Normal 的 VisualState 需要放在最前面,里面可以不写任何的代码,将会自动清空状态
也如上面代码,设置属性的值时候,既可以使用 Setters 的方式,也可以使用动画的方式。使用 Setters 的代码比较短,如下面代码
<VisualState x:Name="PointerOver">
<!-- Hover 效果 -->
<VisualState.Setters>
<Setter Target="ButtonContentPath.Stroke" Value="Transparent"></Setter>
<Setter Target="ButtonContentPath.Fill" Value="Blue"></Setter>
</VisualState.Setters>
</VisualState>
使用 Setters 时不需要管 Property 属性,只需要保证 Target 是 对象.属性
的写法就好了
使用动画的例子如下
<VisualState x:Name="Pressed">
<!-- 按下状态 -->
<Storyboard>
<!-- 颜色用 ColorAnimation 也可以 -->
<!-- <ColorAnimation To=""></ColorAnimation> -->
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonContentPath" Storyboard.TargetProperty="Stroke" >
<DiscreteObjectKeyFrame KeyTime="0" Value="Transparent"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonContentPath" Storyboard.TargetProperty="Fill" >
<DiscreteObjectKeyFrame KeyTime="0" Value="#FF666666"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
动画代码比较多,但是可以实现比较柔和的效果。因为 Setters 是立刻变化的,动画可以实现慢慢变化。对于视觉效果比较大的范围,推荐使用动画
具体一个控件有哪些 VisualState 可以设置,需要查阅文档,详细请看: https://learn.microsoft.com/en-us/uwp/api/windows.ui.xaml.visualstate
了解基础用法之后,咱就可以继续在 ButtonHelper 里面定义鼠标移动到按钮上的边框轮廓颜色画刷附加属性,如下面代码
public class ButtonHelper
{
public static readonly DependencyProperty PointerOverStrokeBrushProperty = DependencyProperty.RegisterAttached(
"PointerOverStrokeBrush", typeof(Brush), typeof(ButtonHelper), new PropertyMetadata(default(Brush)));
public static void SetPointerOverStrokeBrush(DependencyObject element, Brush value)
{
element.SetValue(PointerOverStrokeBrushProperty, value);
}
public static Brush GetPointerOverStrokeBrush(DependencyObject element)
{
return (Brush)element.GetValue(PointerOverStrokeBrushProperty);
}
}
定义完成附加属性,尝试在 XAML 里面使用这个属性。正常的使用方法是会在样式里面,给定附加属性初值的。为什么不在附加属性定义的时候,写附加属性默认值?这是因为不同的样式一般都会有样式自身期望的初值,因此作为样式使用的附加属性,比较少会配置默认值。但也不是不能,取决于你的开森
<Style x:Key="Style.Button.PathButtonStyle" TargetType="Button">
<Setter Property="local:ButtonHelper.PointerOverStroke" Value="#FF666666"/>
...
</Style>
将此 PointerOverStroke 在 PointerOver 赋值给到按钮的 Path 上,代码如下,以下代码使用 Setter 的方式赋值,代码看起来比较短
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="ButtonContentPath.Stroke" Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=(local:ButtonHelper.PointerOverStrokeBrush)}"></Setter>
</VisualState.Setters>
</VisualState>
如此即可完成样式的对鼠标移动到按钮上,按钮使用附加属性配置的颜色的定义
按钮可以通过 PointerOverStrokeBrush 附加属性定义按钮期望鼠标移动到按钮上的颜色画刷,如以下代码
<Button Style="{StaticResource Style.Button.PathButtonStyle}"
local:ButtonHelper.ButtonPath="M5,5L15,15L5,25"
local:ButtonHelper.PointerOverStrokeBrush="Blue">
</Button>
可以看到,在完成样式定义的基础上,只需简单的代码就可以让按钮工作起来了
如果刚好有一组按钮都需要做相同的鼠标移动到按钮上的 Hover 颜色画刷更改,可以再定义一个新的样式,继承 Style.Button.PathButton
样式,如以下代码
<Style x:Key="Style.Button.SlideButtonStyle" TargetType="Button" BasedOn="{StaticResource Style.Button.PathButton}">
<Setter Property="Width" Value="50"></Setter>
<Setter Property="Height" Value="50"></Setter>
<Setter Property="local:ButtonHelper.PointerOverStrokeBrush" Value="#FF996666"/>
</Style>
如此可以在不影响阅读界面代码的前提下,减少界面代码的重复量,也能让界面代码编写更加方便
本文定义的 Style.Button.PathButton
按钮样式的代码如下
<Style x:Key="Style.Button.PathButtonStyle" TargetType="Button">
<Setter Property="Background" Value="Transparent"></Setter>
<Setter Property="HorizontalAlignment" Value="Center"></Setter>
<Setter Property="local:ButtonHelper.PointerOverStrokeBrush" Value="#FF666666"/>
<Setter Property="VerticalAlignment" Value="Center"></Setter>
<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>
<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Target="ButtonContentPath.Stroke" Value="{Binding RelativeSource={RelativeSource TemplatedParent},Path=(local:ButtonHelper.PointerOverStrokeBrush)}"></Setter>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Pressed">
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Path x:Name="ButtonContentPath" StrokeThickness="2" Stroke="#FF666666" Data="{Binding RelativeSource={RelativeSource TemplatedParent},Path=(local:ButtonHelper.ButtonPath)}"></Path>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
本文代码放在 github 和 gitee 上,可以使用如下命令行拉取代码
先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 2eb5da7c4a63d65e1a2424ca40e2ae94f5da7549
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源。请在命令行继续输入以下代码,将 gitee 源换成 github 源进行拉取代码
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 2eb5da7c4a63d65e1a2424ca40e2ae94f5da7549
获取代码之后,进入 UnoDemo/PathButtonDemo 文件夹,即可获取到源代码
更多 UWP 或 WinUI3 或 UNO 开发教程,请参阅 博客导航
UWP WinUI 制作一个路径矢量图标按钮样式入门的更多相关文章
- Expression Blend4经验分享:制作一个简单的图片按钮样式
这次分享如何做一个简单的图片按钮经验 在我的个人Silverlight网页上,有个Iphone手机的效果,其中用到大量的图片按钮 http://raimon.6.gwidc.com/Iphone/de ...
- Expression Blend4经验分享:制作一个简单的文字按钮样式
首先在Grid里放一个TextBlock,对象时间线窗口的结构树如下 右键点击grid,选择构成控件 会弹出构成控件的对话框,选择你要构成的控件类型,控件名称,控件样式存储位置 这里我们选择butto ...
- 用 Swift 制作一个漂亮的汉堡按钮过渡动画
汉堡按钮在界面设计中已经是老生常谈了,但是当我在dribbble看到这个漂亮的过渡动画时,我决定试试用代码实现它. 这是 CreativeDash team 的原型图: 你可能已经注意到了,汉堡顶 ...
- 吴裕雄 Bootstrap 前端框架开发——Bootstrap 按钮:制作一个超小按钮
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- Iconfont(矢量图标)+iconmoon(图标svg互转)配合javascript来打造属于自己的个性化社交分享系统
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_143 每一个应用程序,其实都会有分享的需求,比如一键分享一篇文章或者一些活动到微博或者微信亦或者是twitter等社交平台,因为人 ...
- 8、Semantic-UI之其他按钮样式
8.1 其他按钮样式定义 示例:定义其他按钮样式 定义圆形图标按钮样式 <div class="ui circular icon button"><i class ...
- 阿里UX矢量图标库–最强大的矢量图标库(Icon font制作力荐工具)
继前面介绍过ICON-FONT的制作后,找了几个ICON库都是国外的今天偶然发现阿里巴巴的图标矢量库,www.iconfont.cn用了之后感觉很强大,丰富的图标库(集合阿里妈妈&淘宝的图标库 ...
- CSS3 制作一个边框向周围散开的按钮效果
我们将要达到的是如下的效果(若效果未出现请刷新): 分析 主要还是运用CSS3的transition, animation, transform还有渐变背景等特性. 由于按钮在鼠标进入时有不同的样式, ...
- 使用CSS3制作72个webapp图标
前言 移动网络带宽的快慢直接影响webapp应用体验效果的优差,其中加载图片是很耗流量的,所以对这一方面的性能优化是很需要的.一般对于那些小而多的图片(图标)都会采用sprite合并成一张图片来减少h ...
- Font Awesome 4.0.3 提供了369个网页常用的矢量字体图标,新浪、人人 的矢量图标也到其中哟
要求 必备知识 本文要求基本了解html与css前端代码. 运行环境 普通浏览器,兼容IE7 源码下载 下载地址 Font Awesome 为您提供了一套可缩放的字体矢量图标,可以快速自定义图标的大小 ...
随机推荐
- MySQL面试必备二之binlog日志
本文首发于公众号:Hunter后端 原文链接:MySQL面试必备二之binlog日志 关于 binlog,常被问到几个面试问题如下: binlog 是什么 binlog 都记录什么数据 binlog ...
- 使用 Docker 部署 WebTop 运行 Linux 系统
1)项目介绍 GitHub:https://github.com/linuxserver/docker-webtop WebTop 它是一个基于 Linux ( Ubuntu 和 Alpine 两种版 ...
- Aliplayer通过HLS流式播放Aliyun Mps(媒体处理)转码的加密视频
前言 公司需求,上传的视频需要加上公司Logo,同时播放需要采用流式播放禁止下载. 现有的环境在阿里云上,所以自然想到了阿里云的产品[媒体处理]的转码功能. 转码配置 配置比较简单,采用阿里云HLS标 ...
- java学习之旅(day.13)
常用类 Object类 object类是所有类的父类,所有类直接或间接继承object类 所有类,如果没书写extends显示继承某个类,都默认继承object类 getClass()方法 返回值是c ...
- java学习之旅(day.08)
类与对象的关系 类是一种抽象的数据类型,是对某一类事物的描述,但并不代表具体的事物,如动物与狗的关系,类描述的是某一类事物具备的共同特点 对象是抽象概念的具体实例 能够展现出功能,体现出特点的是具体的 ...
- k8s错误集合
1.etcd没有启动的 [root@mcwk8s03 ~]# kubectl get nodesUnable to connect to the server: context deadline ex ...
- kubernetes 之dashboard
部署 kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.5.0/aio/deploy/recomme ...
- 如此丝滑的API设计,用起来真香
分享是最有效的学习方式. 博客:https://blog.ktdaddy.com/ 故事 工位上,小猫一边撸着代码,一边吐槽着前人设计的接口. 如下: "我艹,货架模型明明和商品SKU模型是 ...
- sass 混合指令 (Mixin Directives)详解
混合指令(Mixin)用于定义可重复使用的样式,避免了使用无语意的 class,比如 .float-left.混合指令可以包含所有的 CSS 规则,绝大部分 Sass 规则,甚至通过参数功能引入变 ...
- sqlServer 重复数据项处理,只选其中一条,保留一条
select * from table where id in (select max(id) from table group by [去除重复的字段名列表,....]) --删除 from tab ...