[WPF] 用 Effect 实现线条光影效果
1. 前言
几个月前 ChokCoco 大佬发布了一篇文章:
在文章里实现了一个发光的心形线条互相追逐的效果:
现在正好有空就试试用 WPF 实现一下。在实现过程中我用到这些知识和技巧:
- Segoe Fluent 图标字体
- 在 Blend 中创建 Path
- 计算 Path 的长途
- Path 的边框动画
- VisualStudio 的设计时数据支持
- 自定义 Effect
这篇文章将讲解如何使用这些知识和技巧模仿他的动画效果。
2. 图标字体和 Path
虽然 ChokCoco 大佬已经给了一个心形的路径,但总不能每次都期待别人给的东西。对于 WPF 开发者来说,用图标字体和 Blend 可以轻松创建一些简单的路径。
首先要找到一个心形的图标字体,在 Windows 10/11 可以直接使用 Segoe MDL2 和 Segoe Fluent 字体,这两个是随 Windows 10/11 发布的系统内置字体。下面的页面列出了可用的 Segoe Fluent 字体:
https://docs.microsoft.com/en-us/windows/apps/design/style/segoe-fluent-icons-font
找到 HeartFill 的 Unicode 码位 eb52
,然后打开 Microsoft Blend for VisualStudio 2019(更新的版本砍掉了这篇文章用到的功能),创建一个 WPF 应用,在 XAML 中输入下面这段 XAML:
这时候应该可以看到一个心形,他就是 HeartFill 的文字图标。在设计视图选中它,右键选择 Path -> Convert to Path(中文版本下应该是 转换为路径):
这样 TextBlock 就被转换为一个相同形状的 Path。接下来将 Fill 设置为空,Stroke 和 StrokeThickness 分别设置为 Black 和 10,Path 的形状就如下图所示,选中左边工具栏的 Pen 工具还可以调整 Path 的形状:
这时候对应的 XAML 如下:
3. 计算 Path 的长途
拿到路径后,下一步需要计算它的长度。这个长度不需要太精确,可以用 GetFlattenedPathGeometry 获取 PathGeometry 对象的多边形近似 Geometry,然后计算每条边的长度:
public double GetLength(Geometry geo)
{
PathGeometry path = geo.GetFlattenedPathGeometry();
double length = 0.0;
foreach (PathFigure pf in path.Figures)
{
Point start = pf.StartPoint;
foreach (PolyLineSegment seg in pf.Segments)
{
foreach (Point point in seg.Points)
{
length += (start - point).Length;
start = point;
}
}
}
return length;
}
4. Path 的边框动画
上一步计算出的 Path 长度是 898。
然后通过 StrokeDashArray 和 StrokeDashOffset 对 Path 做边框动画。因为 Path 的 StrokeThickness 是 10 像素,所以做边框动画时所有数值都要除以 10。
第一步,将 StrokeDashArray 设置为 29.9 59.9
,它将 Path 的边框分成两部分,第一部分为实线,第二部分为空白。
第二步,然后用 DoubleAnimation 使 StrokeDashOffset 从 0 到 89.8 不断循环,实现线条动画的不断循环。
第三步,添加一个相同的 Path,并让它的动画延迟一秒执行,这样就实现了两个心形线条的追逐动画。
有关边框动画的更多内容,可以参考这两篇文章:
5. VisualStudio 的设计时数据
现在我们只差让这两个 Path 发光了。但在这之前我们需要了解 VisualStudio 的设计时数据的概念。
设计时数据是你设置的模拟数据,使控件更易于在 XAML 设计器中进行可视化。d: 前缀用于设置设计时的属性值,它只影响设计视图,不会编译到正在运行的应用中。具体可以参考这篇文档:
在 Visual Studio 中通过 XAML 设计器使用设计时数据
这是一个很实用的小技巧,由于上面的两个 Path 重叠在一起,在设计视图难以区分,所以用了 d:StrokeDashOffset="45"
让其中一个错开。这段内容只在设计视图起作用,不会有其它副作用。
6. 自定义 Effect
在 WPF 中要做发光效果通常都是用 DropShadowEffect ,例如这样:
但这样颜色实在太淡,太淡了。为了解决这个问题,其中一种做法是叠加多个 Path,这样它们的 Drop Shadow 也会叠加起来,实现一个很亮的发光效果。但是这里会需要对叠加的多个 Path 都做动画,恐怕性能会很有问题。
另一种方式是自定义一个 Effect,它的代码只需要如下几行:
float Amount : register(C0);
float4 main(float2 uv : TEXCOORD) : COLOR
{
float4 srcColor = tex2D(input, uv);
srcColor.rgb *= Amount;
srcColor.a *= Amount;
return srcColor;
}
这只是个很简单的 Effect,就是将所有像素的颜色和透明度乘以一个指定值。我不知道这种效果叫什么名字,但因为它最终实现了发光的效果,所以命名为 GlowEffect。使用 GlowEffect 配合 BlurEffect,上面暗淡的颜色就变得明亮起来:
关于自定义 Effect 的更多内容,可以参考 WalterLv 大佬的这篇文章:
WPF 像素着色器入门:使用 Shazzam Shader Editor 编写 HLSL 像素着色器代码
7. 成果
最后的成果如下:
8. 源码
https://github.com/DinoChan/wpf_design_and_animation_lab