控件名称:RiotPlayButton作者:WPFDevelopersOrg - Vicky&James源码链接[1]:https://github.com/vickyqu115/riotplaybutton教学视频[2](【小李趣味多】https://bit.ly/3xI9DNh)这篇文章是对WPF RiotPlayButton
教程视频的技术回顾。摘要本文详细介绍并分析了使用纯WPF
技术开发受《英雄联盟》游戏启发的PLAY
按钮的过程。本文强调了利用WPF
功能创建各种用户界面组件的过程,并为开源开发提供了新的视角。同时,探索了动画和触发器等高级WPF
功能,以提升用户交互体验。介绍用户界面组件在提升用户体验方面非常重要。在游戏中,反应迅速且视觉上有吸引力的PLAY
按钮是通往娱乐世界的门户。本文展示了使用WPF
这一构建丰富桌面应用程序的强大框架创建PLAY
按钮的过程。项目背景本文讨论的项目尽可能全面地展示了WPF
技术的能力。几年前发布这个项目后,获得了积极的反馈,这激励我继续为开源开发做贡献。随着.NET
技术的发展,我不断更新和改进共享在GitHub
上的代码。考虑到整个项目中包含的内容丰富,我决定详细分析每个部分的组成和技术重点,希望能为更多喜欢WPF
技术的人提供帮助。按钮构成通过分析器可以看到,这个PLAY
按钮继承了WPF ToggleButton
的属性。左边是《英雄联盟》游戏的标志,右边包含边界、图像、文本等多种设计元素。此外,还添加了鼠标悬停和检查触发效果。主要内容分析1. 创建不规则形状第一个和第二个图形可以通过使用Border
控件轻松编码。然而,第三个尖角和弧形的图形不能简单地使用Border
编码。因此,尽管最初可以使用多边形和坐标来绘制,但多边形属性不提供绘制弧形的功能。因此,必须使用Path
控件来编码。详细分析 <Style TargetType=\"{x:Type Path}\" x:Key=\"Arrow\"> <Setter Property=\"Fill\" Value=\"#1E2328\"/> <Setter Property=\"Stroke\" Value=\"{StaticResource ArrowStroke}\"/> <Setter Property=\"StrokeThickness\" Value=\"2\"/> <Setter Property=\"Data\" Value=\"M 0,0 L 103,0 L 118,14 L 103,28 L 0,28 C 10,14 0,0 0,0 Z\"/> <Setter Property=\"Margin\" Value=\"40 5 4 -5\"/> <Setter Property=\"Effect\"> <Setter.Value> <DropShadowEffect BlurRadius=\"5\" ShadowDepth=\"2\"/> </Setter.Value> </Setter></Style>
在WPF
中,Path
控件是用于绘制各种形状和轮廓的强大工具。Path
控件通过路径数据定义形状,路径数据是一系列指定如何绘制形状的命令和坐标。Path
控件的主要属性如下:Data 属性:Data 属性是 Path 控件的重要属性,用于指定描述形状轮廓的路径数据。路径数据格式包括 MoveTo (M)、LineTo (L)、CurveTo (C)、ClosePath (Z)
等命令与坐标的结合。Fill 属性: Fill
属性用于指定形状内部的填充颜色,可以使用颜色、渐变、图案或透明度填充形状内部。Stroke 属性: Stroke
属性用于指定形状轮廓的颜色,可以使用各种颜色定义轮廓颜色。StrokeThickness 属性: StrokeThickness
属性用于指定轮廓的厚度,决定轮廓的宽度。命令和坐标:路径数据是命令和坐标的连续体,这些命令指示 WPF
从一个点到另一个点如何绘制形状。常见的路径命令包括:M (MoveTo)
:将绘制点移动到指定坐标。L (LineTo)
:绘制到指定坐标的直线。C (CurveTo)
:使用控制点绘制贝塞尔曲线。Z (ClosePath)
:关闭路径,将当前点连接到起点形成闭合形状。Data
属性是Path
控件的关键属性,包含定义形状轮廓的路径数据。这些路径数据由一系列命令和坐标组成,描述了路径的轮廓。在这个项目中,路径数据的命令和坐标的详细描述如下:可以将其简单地解释为X/Y
坐标轴。将此形状的长度设置为118
,宽度设置为28
:M 0,0
:这是“MoveTo”
命令,将绘制点移动到坐标(0, 0)
作为起点。L 103,0
:这是“LineTo”
命令,从当前点(0, 0)
绘制到坐标(103, 0)
的直线。接着绘制到 (118, 14)、(103, 28)、(0, 28)
的线段。由于这是对称形状,第二条线的Y
坐标是形状总高度的一半,即14
。接下来是绘制曲线的部分:C 10,14 0,0 0,0 z
:这是 “贝塞尔曲线” 命令,定义了前一个点为控制点,后一个点为终点的贝塞尔曲线。这个命令定义了控制点为(10, 14)
,终点为(0, 0)
的贝塞尔曲线,并使用‘z’
命令关闭路径,将其连接回起点(0, 0)
。2. 创建渐变颜色<LinearGradientBrush x:Key=\"ArrowStroke\" StartPoint=\"0.5,0\" EndPoint=\"0.5,1\" > <GradientStop Color=\"#CC3FE7EE\" Offset=\"0\"/> <GradientStop Color=\"#CC006D7D\" Offset=\"0.5\"/> <GradientStop Color=\"#CC0493A7\" Offset=\"1\"/></LinearGradientBrush><LinearGradientBrush x:Key=\"ArrowStrokeOver\" StartPoint=\"0.5,0\" EndPoint=\"0.5,1\" > <GradientStop Color=\"#FFAFF5FF\" Offset=\"0\"/> <GradientStop Color=\"#FF46E6FF\" Offset=\"0.5\"/> <GradientStop Color=\"#FF00ADD4\" Offset=\"1\"/></LinearGradientBrush><LinearGradientBrush x:Key=\"ArrowFillOver\" StartPoint=\"0.5,0\" EndPoint=\"0.5,1\" > <GradientStop Color=\"#FF1D3B4A\" Offset=\"0\"/> <GradientStop Color=\"#FF082734\" Offset=\"1\"/></LinearGradientBrush>
在游戏的这个部分,描边部分并不是简单的纯色,而是由多种色调组成的渐变颜色。为了实现这个效果,我们可以使用LinearGradientBrush
来自定义颜色。LinearGradientBrush 的主要属性及使用方法StartPoint 和 EndPoint: StartPoint
指定渐变的起点,通常使用相对坐标表示。这里 (0, 0) 是左上角,(1, 1) 是右下角。EndPoint
使用相对坐标指定渐变的终点。GradientStops: GradientStops
是GradientStop
对象的集合,每个对象定义颜色和相对位置(Offset)。GradientStop
的Color
属性定义指定位置的颜色,Offset
属性定义渐变中的颜色位置,通常在0
到1
之间。渐变方向:渐变的方向由 StartPoint
和EndPoint
决定。例如,StartPoint
为(0, 0)
,EndPoint
为(1, 1)
时,渐变从左上角到右下角。渐变类型: 在这个项目中,我们希望创建一个从形状中央开始向下移动的垂直渐变。因此,将LinearGradientBrush
默认设置为线性渐变,颜色沿直线渐变。通过调整StartPoint
和EndPoint
,可以改变渐变的方向和起点,生成各种渐变效果。StartPoint
设置为(0.5, 0)
指定渐变的起点为顶部中央,将EndPoint
设置为(0.5, 1)
指定渐变的终点为底部中央。然后,GradientStops
集合包含三个GradientStop
对象,每个对象定义了不同的颜色和相对位置:第一个 GradientStop
:Color
设置为#CC3FE7EE
。Offset
设置为0
,表示该颜色位于渐变的起点。第二个 颜色位于渐变的中间。GradientStop
:Color
设置为#CC006D7D
。Offset
设置为0.5
,表示该第三个 GradientStop
:Color
设置为#CC0493A7
。Offset
设置为 1,表示该颜色位于渐变的终点。处理 在Path
和Border
厚度Border
控件中:Border
控件的边框线包含在Border
内。边框线的厚度由 BorderThickness 属性控制,以设备独立像素(DIPs
)指定边框线的宽度。在Path
控件中:Path
控件的边框线以 StrokeThickness 属性的中心位置为基准绘制。StrokeThickness 控制边框线的厚度,表示边框线从中心延伸的距离。在这个固定大小的图形中,将Border
和Path
的厚度都设置为2
,边距设置为4 4 4 4
。但在这种设置下,可以看到Path
的上边框超出了Border
。因此,需要根据StrokeThickness
调整Path
的边距。左边距已经设置为40
,可以覆盖GreenLine
,所以没有问题。上边距需要增加1
像素,设置为5
像素,右边距和下边距不需要更改。由于Path
的尺寸固定为118x28
,只需调整左边和上边的边距。此外,上边距增加5
像素后,下边看起来可能会被裁剪。为避免这种情况,可以将下边距设置为-5
像素,这样上边增加的5
像素会被去掉,布局会保持平衡。另一种方法是保持下边距为0
像素。这两种方法都可以通过增加上边距来防止下边被裁剪。4. 使用 Jamesnet.WPF Nuget 生成动画在WPF
中,可以生成各种动态动画来使用户界面更加有趣。在这个项目中,使用厚度动画为TextBlock
的文本部分添加有趣的动画效果。<Application x:Class=\"VickyPlayButton.App\" xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\" xmlns:james=\"https://jamesnet.dev/xaml/presentation\" StartupUri=\"MainWindow.xaml\"> <Application.Resources> <Style TargetType=\"{x:Type ToggleButton}\"> <Setter Property=\"Height\" Value=\"38\"/> <Setter Property=\"Width\" Value=\"165\"/> <Setter Property=\"Foreground\" Value=\"#FFFFFF\"/> <Setter Property=\"Background\" Value=\"Transparent\"/> <Setter Property=\"Template\"> <Setter.Value> <ControlTemplate TargetType=\"{x:Type ToggleButton}\"> <ControlTemplate.Resources> <Storyboard x:Key=\"Checked\"> <james:ThickItem Mode=\"CubicEaseInOut\" TargetName=\"play\" Property=\"Margin\" Duration=\"0:0:0:0.5\" To=\"30 100 0 0\"/> <james:ThickItem Mode=\"CubicEaseInOut\" TargetName=\"stop\" Property=\"Margin\" Duration=\"0:0:0:0.5\" To=\"30 0 0 0\"/> </Storyboard> <Storyboard x:Key=\"UnChecked\"> <james:ThickItem Mode=\"CubicEaseInOut\" TargetName=\"play\" Property=\"Margin\" Duration=\"0:0:0:0.5\" To=\"30 0 0 0\"/> <james:ThickItem Mode=\"CubicEaseInOut\" TargetName=\"stop\" Property=\"Margin\" Duration=\"0:0:0:0.5\" To=\"30 0 0 100\"/> </Storyboard> </ControlTemplate.Resources> <Grid Background=\"{TemplateBinding Background}\"> <Border Style=\"{StaticResource GoldLine}\"/> <Image Style=\"{StaticResource Emblem}\"/> <Border Style=\"{StaticResource GreenLine}\"/> <Path x:Name=\"path\" Style=\"{StaticResource Arrow}\"/> <Grid> <Grid.Clip> <RectangleGeometry Rect=\"0,5,165,28\"/> </Grid.Clip> <TextBlock x:Name=\"play\" Style=\"{StaticResource Play}\"/> <TextBlock x:Name=\"stop\" Style=\"{StaticResource Stop}\"/> </Grid> </Grid> <ControlTemplate.Triggers> <Trigger Property=\"IsMouseOver\" Value=\"True\"> <Setter TargetName=\"path\" Property=\"Fill\" Value=\"{StaticResource ArrowFillOver}\"/> <Setter TargetName=\"path\" Property=\"Stroke\" Value=\"{StaticResource ArrowStrokeOver}\"/> <Setter Property=\"Foreground\" Value=\"#FFFCF1DC\"/> <Setter Property=\"Cursor\" Value=\"Hand\"/> </Trigger> <Trigger Property=\"IsChecked\" Value=\"True\"> <Setter TargetName=\"path\" Property=\"Fill\" Value=\"#1E2328\"/> <Setter TargetName=\"path\" Property=\"Stroke\" Value=\"#5C5B57\"/> <Setter Property=\"Foreground\" Value=\"#3C3C41\"/> <Trigger.EnterActions> <BeginStoryboard Storyboard=\"{StaticResource Checked}\"/> </Trigger.EnterActions> <Trigger.ExitActions> <BeginStoryboard Storyboard=\"{StaticResource UnChecked}\"/> </Trigger.ExitActions> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Application.Resources></Application>
动画可以通过 ControlTemplate.Resources 定义,定义了“Checked”
和“UnChecked”
两个动画资源。在“Checked”
状态下,“Play”
文本消失,“Stop”
文本出现;在“UnChecked”
状态下,“Stop”
文本消失,“Play”
文本出现。这就创建了一个翻转效果的动画。为了更容易地创建和使用动画,我将各种WPF
动画编译成了Jamesnet.WPF Nuget
包。只需添加这个包,就可以轻松地使用和编写动画。5. 使用 Grid.Clip 属性<Grid Background=\"{TemplateBinding Background}\"> <Border Style=\"{StaticResource GoldLine}\"/> <Image Style=\"{StaticResource Emblem}\"/> <Border Style=\"{StaticResource GreenLine}\"/> <Path x:Name=\"path\" Style=\"{StaticResource Arrow}\"/> <Grid> <Grid.Clip> <RectangleGeometry Rect=\"0,5,165,28\"/> </Grid.Clip> <TextBlock x:Name=\"play\" Style=\"{StaticResource Play}\"/> <TextBlock x:Name=\"stop\" Style=\"{StaticResource Stop}\"/> </Grid> </Grid>
由于Grid
内的元素相互重叠,在创建文本上下滚动动画时,可能会出现文本超出边框的视觉问题。为了解决这个问题,使用<Grid.Clip>
属性。<Grid.Clip>
是一个XAML
元素,用于定义子元素的可见区域。剪辑区域通常是一个矩形,只有剪辑区域内的内容才会显示,超出部分将被隐藏。在这个项目中,<Grid.Clip>
区域设置在Path
的尺寸范围内:Rect=\"0,5,165,28\"
。这样,文本只会在这个区域内显示,从而在Path
内实现上下滚动的效果。 视频内容纠正最近有一位观众指出了我们视频中的一个错误,我在此做出更正和说明。11:34
贝塞尔曲线里C
不是弧线的中点吧,控制点是在线外边的“贝塞尔曲线中,C
点不是曲线的中点。控制点是在曲线外的。”在11:34
的视频里,会看到以下内容(图片与视频说明一致)。经过重新审视,我发现确实在解释贝塞尔曲线时出现了误解。感谢这位观众的指正,现在我来进行修正。首先,视频中使用的路径如下:M 0,0 L 103,0 L 118,14 L 103,28 L 0,28 C 10,14 0,0 0,0 ZY轴的0,0基准反转对理解没有太大影响,请大家谅解。通过生成的图表可以看到,C (10, 14)
控制点的位置实际上在曲线的外部,而不是中点。这正是观众指出的问题。接下来,我们详细了解一下三次贝塞尔曲线的机制。三次贝塞尔曲线需要4
个点:P0, P1, P2, P3
。根据当前的几何路径数据,分别映射如下:P0: 0,28 (起点)P1: 10,14 (控制点1)P2: 0,0 (控制点2)P3: 0,0 (终点)此外,我们绘制了曲线随时间变化的过程。蓝色线段的起点和终点随着控制点的移动而绘制出曲线。具体的可视化效果请参见这里[3],该博客中有很好的动画说明。在这种情况下,使用二次贝塞尔曲线比三次贝塞尔曲线更合适,因为二次贝塞尔曲线需要的控制点更少。三次贝塞尔曲线 (Cubic Bezier)M 0,0 L 103,0 L 118,14 L 103,28 L 0,28 C 10,14 0,0 0,0 Z二次贝塞尔曲线(Quadratic Bezier)M 0,0 L 103,0 L 118,14 L 103,28 L 0,28 Q 10,14 0,0 Z综上所述,在这种情况下使用二次贝塞尔曲线更为合适。沟通与支持我们随时保持沟通渠道开放。大家可以通过以下方式与我们互动:GitHub[4]: 关注、Fork、Stars BiliBili[5]: 一键三连 邮箱: james@jamesnet.dev 参考资料[1]源码链接: https://github.com/vickyqu115/riotplaybutton[2]教学视频: https://bit.ly/3xI9DNh[3]这里: https://blog.naver.com/kyuniitale/40022945907[4]GitHub: https://github.com/vickyqu115/smartdate[5]BiliBili: https://bit.ly/3xI9DNh
(图片来源网络,侵删)
0 评论