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

目录

介绍

介绍

项目背景

按钮组成

关键内容分析

1. 创建不规则形状

详细分析

2. 创建渐变颜色

LinearGradientBrush的主要属性和用法

3. 处理厚度的路径和边框

4. 使用Jamesnet.WPF Nuget创建动画

5.使用Clip属性


介绍

本文详细介绍了如何使用纯WPF技术开发受游戏英雄联盟启发的 PLAY 按钮。它强调利用WPF功能创建通用用户界面组件的过程,并为开源开发提供了新的视角。本文还探讨了高级WPF功能(如动画和触发器),以增强用户交互体验。

我们已将本文的内容制作成教学视频,并发布在YouTube上,每个人都可以与视频一起练习。

Youtube英雄联盟PlayButton- WPF项目
GitHub英雄联盟PLAY按钮- WPF项目
GitHub英雄联盟完整项目-WPF

介绍

用户界面组件对于增强用户体验至关重要。在游戏中,响应迅速且具有视觉吸引力的 PLAY 按钮是通往娱乐世界的门户。本文演示了使用WPF创建 PLAY 按钮的过程,WPF为构建丰富的桌面应用程序提供了一个强大的框架。

项目背景

本文中讨论的项目旨在尽可能全面地展示WPF技术的功能。几年前,我们发布了这个项目,并收到了巨大的积极回应,这继续激励我们为开源开发做出贡献。随着.NET技术的发展,我们不断更新和完善以前在GitHub上共享的代码。鉴于整个项目涵盖的内容广泛,我们决定将其分解并详细分析每个部分的组成和技术重点,希望能帮助更多的WPF爱好者完成他们的学习之旅。

按钮组成

通过使用分析器,我们可以看到这个PLAY按钮继承了WPFToggleButton的属性。左侧有一个来自英雄联盟游戏的标志,而右侧则包含多个元素,例如具有不同设计的边框、图像和文本。此外,还添加了交互式鼠标悬停和选中的触发效果。

关键内容分析

1. 创建不规则形状

前两个图形可以使用边框控件轻松编码。但是,第三个图形(包括尖头和圆弧)不能使用简单的边框进行编码。因此,我们最初的想法可能是使用多边形和坐标进行绘制。尽管如此,该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属性是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):闭合路径,将当前点与起点连接起来,形成闭合形状。

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的主要属性和用法

  • StartPointEndPoint
    StartPoint指定渐变的起点,通常使用相对坐标表示,其中(0, 0)为左上角,(1, 1)为右下角。EndPoint指定渐变的终点,也使用相对坐标。
  • GradientStops
    GradientStopsGradientStop对象的集合,每个对象定义一种颜色和相对位置(Offset)。GradientStopColor属性定义指定位置的颜色,Offset属性定义颜色在渐变中的位置,通常范围为01
  • Gradient方向
    Gradient的方向由StartPointEndPoint决定。例如,如果StartPoint(0, 0)EndPoint(1, 1),Gradient将从左上角过渡到右下角。
  • Gradient类型
    LinearGradientBrush默认为线性渐变,其中颜色沿直线过渡。通过调整StartPointEndPoint,可以更改Gradient的方向和起点以创建各种Gradient效果。

在这个项目中,我们的目标是创建一个垂直渐变,从形状的中心开始,向下移动。因此,我们设置StartPoint(0.5, 0),表示梯度的起点位于顶部中心(水平中点)。EndPoint设置为(0.5, 1),表示渐变的终点位于底部中心(水平中点)。

接下来,该GradientStops集合包括三个GradientStop对象,每个对象定义不同的颜色和相对位置:

  1. 第三个GradientStop
    Color设置为#CC3FE7EE,表示颜色值。
    Offset设置为0,表示此颜色位于gradient的起点。
  2. 第二个GradientStop
    Color设置为#CC006D7D
    Offset设置为0.5,表示此颜色位于gradient的中点。
  3. 第三个GradientStop
    Color设置为#CC0493A7
    Offset设置为1,表示此颜色位于gradient的终点。

3. 处理厚度的路径和边框

Border控件中:

  • Border控件的边界包含在控件Border本身中。边界线的粗细由BorderThickness属性控制,以设备无关的像素(DIP)为单位指定边界线的宽度。

Path控件中:

  • Path控件的边界线是根据StrokeThickness属性的中心位置绘制的。 StrokeThickness控制边界线的粗细,表示边界线从中心延伸的距离。

在此固定大小的图形中,BorderPath两者的厚度都设置为2,并且Margin设置为4 4 4 4。但是,此设置显示Path的上边界延伸到了Border之外。

因此,考虑到StrokeThickness, PathMargin级需要调整。左边距已设置为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中,可以创建各种动态动画,使用户界面更具吸引力。在此项目中,厚度动画用于向TextBlock的文本部分添加有趣的动画。

可以使用ControlTemplate.Resources定义动画,这允许您定义两个动画资源:CheckedUnChecked。选中后,将删除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内的上下滚动效果。

https://www.codeproject.com/Articles/5373396/Creating-a-League-of-Legends-Inspired-Play-Button

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值