WPF使用Shape实现复杂线条动画
看到巧用 CSS/SVG 实现复杂线条光效动画的文章,便也想尝试用WPF的Shape配合动画实现同样的效果。ChokCoco大佬的文章中介绍了基于SVG的线条动画效果和通过角向渐变配合 MASK 实现渐变线条两种方式。WPF中的Shape与SVG非常相似,因此这种方式也很容易实现。但WPF中仅有的两种渐变画刷不包含角向渐变,本文使用了另外两种方式实现同样的效果。
在Avalonia的API文档中有看到ConicGradientBrush,应该可以用角向渐变的方式来实现。
首先看一下三种方式实现的效果(录制的gif中颜色存在一些偏差,动画有些卡顿,实际效果要好一些):

基于Polyline的线条动画效果
这种方式也是利用StrokeDashArray实现虚线样式,然后通过动画设置StrokeDashOffset来实现动画。首先,用Polyline绘制一段折线:
<Polyline Points="240 20 140 20 140 100 0 100" Stroke="#ddd" />
这样,我们就得到一条这样的折线:

接下来,利用StrokeDashArray实现与上边折线相同路径的虚线(点划线):
<Polyline Points="240 20 140 20 140 100 0 100" Stroke="red"
StrokeDashArray="20 30" />

StrokeDashArray设置了虚线(点划线)中实线段的长度以及间隔,这里和SVG中的stroke-dasharray略有不同,WPF中StrokeDashArray使用的是相对值。例如此处设置的StrokeDashArray="20 30"表示实线段长度为20,间隔为30,这些值是相对于线段的宽度StrokeThickness。如果StrokeThickness=2,那么实线段长度就是40个设备无关单位(Device Independent Units),间隔就是60DIUs。
当我们把间隔设置足够大时,就可以只看到一条实线段,这里折线中三条线段总长是320,因此把实线段设置20,间隔设置300:
<Polyline Points="240 20 140 20 140 100 0 100" Stroke="red"
StrokeDashArray="20 300" />

接下来就是借助StrokeDashOffset来实现动画。
<Grid
Grid.Row="0"
Grid.Column="0"
Margin="5">
<Polyline Points="240 20 140 20 140 100 0 100" Stroke="#ddd" />
<Polyline
Points="240 20 140 20 140 100 0 100"
Stroke="red" StrokeThickness=""
StrokeDashArray="20 300">
<Polyline.Triggers>
<EventTrigger RoutedEvent="Polyline.Loaded">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever" Storyboard.TargetProperty="StrokeDashOffset">
<DoubleAnimation
From="0"
To="-320"
Duration="0:0:3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Polyline.Triggers>
</Polyline>
</Grid>
与CSS/SVG实现的方式一样,WPF中也只能对整段虚线设置渐变色,无法对其中一段实线设置。要想实现渐变效果只能另寻他法。
基于多条线段的动画
最朴素的想法就是用一条渐变色的线段沿着折线的路径移动,但是最大的问题在于折线拐角处难以处理。最为粗暴简单的思路就是针对折线的三段准备三条线段,第一条线段动画即将结束时,第二条开始,第二条动画即将结束时第三条开始。
<Polyline Points="240 20 140 20 140 100 0 100" Stroke="#ddd" />
<Polyline
x:Name="polyline1"
Points="260 20 240 20"
Stroke="{StaticResource linearBrush}" />
<Polyline
x:Name="polyline2"
Points="140 0 140 20"
Stroke="{StaticResource linearBrush}" />
<Polyline
x:Name="polyline3"
Points="160 100 140 100"
Stroke="{StaticResource linearBrush}" />

这里有个细节需要注意,第1条线段向左移动刚好离开折线水平轨迹时,第2条线段才开始向下延垂直轨迹移动,并且移动速度一致,才能保证形成的移动的线段颜色连贯且长度不变。
<Storyboard x:Key="moveLines" RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="polyline1" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="00:00:00.800" Value="-100" />
<EasingDoubleKeyFrame KeyTime="00:00:01" Value="-121" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="polyline2" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)">
<EasingDoubleKeyFrame KeyTime="00:00:00.800" Value="0" />
<EasingDoubleKeyFrame KeyTime="00:00:01" Value="20" />
<EasingDoubleKeyFrame KeyTime="00:00:01.8" Value="80" />
<EasingDoubleKeyFrame KeyTime="00:00:02" Value="101" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="polyline3" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="00:00:01.8" Value="0" />
<EasingDoubleKeyFrame KeyTime="00:00:02" Value="-20" />
<EasingDoubleKeyFrame KeyTime="00:00:03" Value="-160" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>

这样看效果并不明显,接下来就是需要用一个形状把完成动画的线段遮挡起来:
<Polygon Fill="#fff" Points="240,19 139,19 139,99 0,99 0,79 119,79 119,0 240,0 240,19 240,21 141,21 141,101 0,101 0,99 -20,99 -20,101 -20,122 161,122 161,41 260,41 260,19" />

这样基本实现了渐变色线条的动画效果,但终究不够优雅。
基于等腰三角形的动画
上一种方法中,在拐角处由两条线段配合的动画实现的效果,一条线段移出,另一条移入,连接起来刚好是个等腰直角三角形。

然后用线性渐变色填充三角形就可以实现移出的线段颜色和移入部分颜色相同。
<LinearGradientBrush x:Key="linearBrush" StartPoint="0 1" EndPoint="1 0">
<GradientStop Offset="0.25" Color="#399953" />
<GradientStop Offset="0.5" Color="#fbb300" />
<GradientStop Offset="0.75" Color="#d53e33" />
<GradientStop Offset="1" Color="#377af5" />
</LinearGradientBrush>
<Polygon
x:Name="trigle"
Fill="{StaticResource linearBrush}"
Points="240 19 240 40 220 19"/>
接下来就是三角形沿着轨迹移动的动画以及遮挡轨迹以外部分了。
<Storyboard x:Key="moveanimation" RepeatBehavior="Forever">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="trigle" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
<EasingDoubleKeyFrame KeyTime="00:00:00" Value="20" />
<EasingDoubleKeyFrame KeyTime="00:00:01" Value="-99" />
<EasingDoubleKeyFrame KeyTime="00:00:02" Value="-99" />
<EasingDoubleKeyFrame KeyTime="00:00:03" Value="-240" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="trigle" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)">
<EasingDoubleKeyFrame KeyTime="00:00:01" Value="0" />
<EasingDoubleKeyFrame KeyTime="00:00:02" Value="80" />
<EasingDoubleKeyFrame KeyTime="00:00:03" Value="80" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Grid Grid.Row="0" Grid.Column="1">
<Polyline Points="240 20 140 20 140 100 0 100" Stroke="#ddd" />
<Polygon
x:Name="trigle"
Fill="{StaticResource linearBrush}"
Points="240 19 240 40 220 19">
<Polygon.RenderTransform>
<TranslateTransform />
</Polygon.RenderTransform>
<Polygon.Triggers>
<EventTrigger RoutedEvent="Polygon.Loaded">
<BeginStoryboard Storyboard="{StaticResource moveanimation}" />
</EventTrigger>
</Polygon.Triggers>
</Polygon>
<Polygon Fill="#fff" Points="240,19 139,19 139,99 0,99 0,79 119,79 119,0 240,0 240,19 240,21 141,21 141,101 0,101 0,99 -20,99 -20,101 -20,122 161,122 161,41 260,41 260,19" d:IsHidden="True" />
</Grid>

小结
基于Polyline的线条StrokeDashOffset的方式最为灵活简洁,不仅适用于直角折线,还适用于各种曲线。如果把此处的Polyline换成一个Ellipse,就可以实现简单的转圈圈等待的动效,但其不足在于线条样式美化空间有限。
基于多条线段的动画可以美化线条,但只适用于Polyline或者直线组成的Path,一旦存在曲线就不适用了。
基于等腰三角形的动画可以看做是基于多条线段的动画的一种特殊场景,局限性较大,仅适用于带直角的折线。
WPF使用Shape实现复杂线条动画的更多相关文章
- 【Web动画】SVG 实现复杂线条动画
在上一篇文章中,我们初步实现了一些利用基本图形就能完成的线条动画: [Web动画]SVG 线条动画入门 当然,事物都是朝着熵增焓减的方向发展的,复杂线条也肯定比有序线条要多. 很多时候,我们无法人工去 ...
- 【Web动画】SVG 线条动画入门
通常我们说的 Web 动画,包含了三大类. CSS3 动画 javascript 动画(canvas) html 动画(SVG) 个人认为 3 种动画各有优劣,实际应用中根据掌握情况作出取舍,本文讨论 ...
- PPT制作线条动画
0.小叙闲言 今天在用PPT做动画的时候小有心得,百度了一下线条动画制作,有一个贴子里面的讨论,也给了我一些灵感,贴子地址:http://www.rapidbbs.cn/thread-24577-1- ...
- 炫酷线条动画--svg
我们经常可以在一些页面看到看起来很酷的线条动画,有些可以用css实现,有些css就无能为力了,今天来研究另一种实现方式,svg 如果对svg是什么还不了解的话,可以先去看看svg的基础教程: 一般对于 ...
- WPF实现QQ群文件列表动画(二)
上篇(WPF实现QQ群文件列表动画(一))介绍了WPF实现QQ群文件列表动画的大致思路,结合我之前讲过的WPF里ItemsControl的分组实现,实现起来问题不大,以下是效果图: 其实就是个List ...
- SVG动画实践篇-无中生有的线条动画
git: https://github.com/rainnaZR/svg-animations/tree/master/src/pages/step2/path 说明 这个动画实现的是线条动画,主要用 ...
- WPF中自动增加行(动画)的TextBox
原文:WPF中自动增加行(动画)的TextBox WPF中自动增加行(动画)的TextBox WPF中的Textbox控件是可以自动换行的,只要设置TextWrapping属性为"Wrap& ...
- WPF入门(三)->几何图形之线条(LineGeometry)
原文:WPF入门(三)->几何图形之线条(LineGeometry) 前一章我们对wpf的xaml语言有了一定的了解,那么我们现在开始来学习如何使用wpf来画出几何图形. LineGeometr ...
- WPF中的PathAnimation(路径动画)
原文:WPF中的PathAnimation(路径动画) WPF中的PathAnimation(路径动画) ...
- 线条之美,玩转SVG线条动画
线条之美,玩转SVG线条动画 作者:AlloyTeam www.alloyteam.com/2017/02/the-beauty-of-the-lines-break-lines-svg-animat ...
随机推荐
- Git 安全远程访问:SSH 密钥对生成、添加和连接步骤解析
使用 SSH 密钥对的 Git 安全远程访问:生成.添加和连接 SSH(Secure Shell)是一种用于安全远程访问的协议,它提供了加密通信和身份验证机制.在使用 SSH 连接到远程 Git 存储 ...
- 学习笔记——Python基础
字符串索引 str = '我是一名学生' print(str[0]) #输出"我" print(str[-6]) #输出"我" 字符串切片:把数据对象的一部分拿 ...
- Avalonia中的布局
Avalonia是一个跨平台的.NET UI框架,它允许开发者使用C#和XAML来创建丰富的桌面应用程序.在Avalonia中,Alignment.Margin和Padding是非常重要的布局属性,它 ...
- np.squeeze()
np.squeeze() 是 NumPy 库中的一个函数,用于从数组中删除单维度的条目.它返回一个在输入数组中删除了尺寸为 1 的维度的新数组. 下面是使用 np.squeeze() 的示例代码: 点 ...
- 重学c#系列——DiagnosticListener [三十五]
前言 简单介绍一下DiagnosticListener,一个比较常见的事件通知模型,可以说是事件发布订阅模型,常用于监控. 正文 直接编写代码: using System.Diagnostics; p ...
- Python - PEP572: 海象运算符
海象运算符 PEP572 的标题是「Assignment Expressions」,也就是「赋值表达式」,也叫做「命名表达式」 不过它现在被广泛的别名是「海象运算符」(The Walrus Opera ...
- Vue3实现图片滚轮缩放和拖拽
在项目开发中遇到一个需求: 1:用鼠标滚轮可对图片进行缩放处理 2:点击按钮可对图片进行缩放处理 3:可对图片进行拖拽处理 我在开发中通过自己实现与百度查看优秀的铁子进行了两种类型的使用 <te ...
- 技术人如何"结构化"高效思考?
1 前言 你是否在解决问题时,常常觉得脑子很乱,很多零散的信息迸发出来,但就是无法形成解决方案. 之所以这样,原因就在于,我们的大脑处理不了太多零散而复杂的信息.人类大脑在处理信息的时候,有两个规律: ...
- 【pytorch学习】之微积分
4 微积分 在2500年前,古希腊人把一个多边形分成三角形,并把它们的面积相加,才找到计算多边形面积的方法.为了求出曲线形状(比如圆)的面积,古希腊人在这样的形状上刻内接多边形.如图所示,内接多边形的 ...
- SpringBoot Admin2.0 集成 Java 诊断神器 Arthas 实践
简介: 项目最初使用 Arthas 主要有两个目的: 1. 通过 arthas 解决实现测试环境.性能测试环境以及生产环境性能问题分析工具的问题. 2. 通过使用 jad.mc.redefine 功能 ...