创建一个受“英雄联盟”启发的 WPF 播放按钮






4.84/5 (16投票s)
本文详细解释和分析了如何使用纯 WPF 技术开发一个受《英雄联盟》游戏启发的 PLAY 按钮。
引言
本文详细解释和分析了如何使用纯 WPF 技术开发一个受《英雄联盟》游戏启发的PLAY按钮。它强调了利用 WPF 功能创建多功能用户界面组件的过程,并为开源开发提供了新的视角。文章还探讨了诸如动画和触发器等高级 WPF 功能,以增强用户交互体验。
我们将本文的内容制作成了教学视频并发布到了 YouTube,大家可以在视频中跟着一起练习。
YouTube:《英雄联盟》播放按钮- WPF 项目
GitHub:《英雄联盟》播放按钮 - WPF 项目
GitHub:《英雄联盟》完整项目 - WPF
引言
用户界面组件对于提升用户体验至关重要。在游戏中,一个响应迅速且视觉吸引人的PLAY按钮是通往娱乐世界的门户。本文展示了使用 WPF 创建PLAY按钮的过程,WPF 是构建丰富桌面应用程序的强大框架。
项目背景
本文讨论的项目旨在尽可能全面地展示 WPF 技术的强大功能。我们几年前发布了这个项目,并获得了巨大的积极反响,这持续激励着我们为开源开发做出贡献。随着 .NET 技术的不断发展,我们一直在更新和完善之前在 GitHub 上共享的代码。鉴于该整体项目涵盖的内容非常广泛,我们决定将其分解,并详细分析每个部分的组成和技术重点,希望能帮助更多 WPF 爱好者学习。
按钮组成
通过分析器,我们可以看到这个PLAY按钮继承了WPFToggleButton
的属性。左侧是《英雄联盟》游戏的一个标志,而右侧则包含边框、图像和具有不同设计的文本等多个元素。此外,还添加了交互式的鼠标悬停和选中触发器效果。
关键内容分析
1. 创建不规则形状
前两个图形可以使用 Border 控件轻松编码。但是,第三个图形包含一个尖端和一个弧形,无法使用简单的 Border 进行编码。因此,我们最初的想法可能是使用 Polygon 和坐标进行绘制。然而,Polygon
属性无法提供绘制弧线的功能。因此,我们应该使用 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
控件使用路径数据来定义形状,而路径数据由一系列命令和坐标组成,用于指定如何绘制形状。
它的基本属性包括
-
Data 属性:
Data
属性是Path
控件的一个关键属性,用于指定路径数据,路径数据由一系列命令和坐标组成,用于描述形状的轮廓。路径数据的格式包括各种命令,如MoveTo
(M)、LineTo
(L)、CurveTo
(C)、ClosePath
(Z)等,并结合坐标来定义形状。通过在Data
属性中提供路径数据,我们可以创建各种形状,包括直线段、曲线、多边形等。 -
Fill 属性:
Fill
属性用于指定形状内部的填充颜色。它允许我们使用纯色、渐变、图案或透明度来填充形状的内部。 -
Stroke 属性:
Stroke
属性用于指定形状轮廓的颜色。它允许我们使用各种颜色来定义轮廓线的颜色。 -
StrokeThickness 属性:
StrokeThickness
属性用于指定轮廓线的粗细。它决定了轮廓线的宽度。 -
命令和坐标:路径数据由一系列命令和坐标组成,其中这些命令指示 WPF 如何从一个点绘制到另一个点。常见的路径命令包括
- M (
MoveTo
):将绘图点移动到指定的坐标。 - L (
LineTo
):在当前点和指定的坐标之间绘制一条直线。 - C (
CurveTo
):绘制一条贝塞尔曲线,使用控制点来定义曲线的形状。 - Z (
ClosePath
):关闭路径,将当前点连接到起始点,形成一个封闭的形状。
- M (
Data
属性是Path
控件的一个关键属性,用于指定路径数据,其中包含用于定义形状轮廓的命令和坐标。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
,表示此颜色位于渐变的结束点。
3. 处理 Path 和 Border 的粗细
在 Border
控件中
Border
控件的边框线包含在Border
本身内部。边框线的粗细由BorderThickness
属性控制,该属性以设备无关像素 (DIP) 为单位指定边框线的宽度。
在 Path
控件中
Path
控件的边框线是根据StrokeThickness
属性的中心位置绘制的。StrokeThickness
控制边框线的粗细,表示边框线从中心延伸的距离。
在这个固定大小的图形中,Border
和Path
的粗细都设置为2
,并且Margin
设置为4 4 4 4
。然而,这种设置表明Path
的上边框超出了Border
。
因此,需要根据StrokeThickness
调整Path
的Margin
。左侧的 Margin 已设置为40
,可以覆盖GreenLine
,因此没有问题。顶部的 Margin 应增加 1 像素,设置为 5 像素,而右侧和底部的 Margin 无需更改。由于Path
的大小固定为 118x28,因此只需调整左侧和顶部的Margin
。
此外,由于顶部的Margin
增加了 5 像素,底部可能会显得被截断,如本例所示。为了防止这种情况,您可以将底部的Margin
设置为-5
像素。这通过移除顶部添加的 5 像素来平衡布局。另一种方法是将底部的Margin
保留为0
像素。这两种方法都可以防止由于顶部Margin
的增加而导致底部被截断。
4. 使用 Jamesnet.WPF Nuget 创建动画
<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>
在 WPF 中,您可以创建各种动态动画,使用户界面更具吸引力。在此项目中,使用 Thickness 动画为TextBlock
的文本部分添加有趣的动画。
动画可以使用ControlTemplate.Resources
定义,它允许您定义两个动画资源:“Checked
”和“UnChecked
”。选中时,“Play
”文本消失,“Stop
”文本移入;而在“UnChecked
”状态下,“Stop
”文本消失,“Play
”文本移入。这会产生一个类似翻转效果的动画。
为了方便创建和使用动画,我们将 WPF 的各种动画进行编译和组织,打包到 Jamesnet.WPF Nuget 包中。只需添加此包,即可轻松使用和编写动画。
5. 使用 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
内实现了上下滚动的效果。
历史
- 2023年11月29日:初始版本