看到巧用 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实现复杂线条动画的更多相关文章

  1. 【Web动画】SVG 实现复杂线条动画

    在上一篇文章中,我们初步实现了一些利用基本图形就能完成的线条动画: [Web动画]SVG 线条动画入门 当然,事物都是朝着熵增焓减的方向发展的,复杂线条也肯定比有序线条要多. 很多时候,我们无法人工去 ...

  2. 【Web动画】SVG 线条动画入门

    通常我们说的 Web 动画,包含了三大类. CSS3 动画 javascript 动画(canvas) html 动画(SVG) 个人认为 3 种动画各有优劣,实际应用中根据掌握情况作出取舍,本文讨论 ...

  3. PPT制作线条动画

    0.小叙闲言 今天在用PPT做动画的时候小有心得,百度了一下线条动画制作,有一个贴子里面的讨论,也给了我一些灵感,贴子地址:http://www.rapidbbs.cn/thread-24577-1- ...

  4. 炫酷线条动画--svg

    我们经常可以在一些页面看到看起来很酷的线条动画,有些可以用css实现,有些css就无能为力了,今天来研究另一种实现方式,svg 如果对svg是什么还不了解的话,可以先去看看svg的基础教程: 一般对于 ...

  5. WPF实现QQ群文件列表动画(二)

    上篇(WPF实现QQ群文件列表动画(一))介绍了WPF实现QQ群文件列表动画的大致思路,结合我之前讲过的WPF里ItemsControl的分组实现,实现起来问题不大,以下是效果图: 其实就是个List ...

  6. SVG动画实践篇-无中生有的线条动画

    git: https://github.com/rainnaZR/svg-animations/tree/master/src/pages/step2/path 说明 这个动画实现的是线条动画,主要用 ...

  7. WPF中自动增加行(动画)的TextBox

    原文:WPF中自动增加行(动画)的TextBox WPF中自动增加行(动画)的TextBox WPF中的Textbox控件是可以自动换行的,只要设置TextWrapping属性为"Wrap& ...

  8. WPF入门(三)->几何图形之线条(LineGeometry)

    原文:WPF入门(三)->几何图形之线条(LineGeometry) 前一章我们对wpf的xaml语言有了一定的了解,那么我们现在开始来学习如何使用wpf来画出几何图形. LineGeometr ...

  9. WPF中的PathAnimation(路径动画)

    原文:WPF中的PathAnimation(路径动画) WPF中的PathAnimation(路径动画)                                                 ...

  10. 线条之美,玩转SVG线条动画

    线条之美,玩转SVG线条动画 作者:AlloyTeam www.alloyteam.com/2017/02/the-beauty-of-the-lines-break-lines-svg-animat ...

随机推荐

  1. #模拟#洛谷 2327 [SCOI2005]扫雷

    题目 分析 考虑最多只有两种情况,因为确定一个位置其它位置随即也能确定, 那么指定第一个位置有没有雷然后判断一下后面推出的雷数是否为0或1,不是显然不行 代码 #include <cstdio& ...

  2. 在typescript中,Omit是什么意思

    在TypeScript中,Omit<Type, Keys> 是一个工具类型(utility type),它用于创建一个新的类型,这个新类型是从现有类型(Type)中排除了某些指定的属性(K ...

  3. ES6中模块化详解

    前言 因为ES6中的模块化是将来,所以就必须有必要好好的了解一下,学习一下,这篇文章就简单总结一下ES6中模块的概念,语法和用法.纯属个人总结,不喜勿喷. 下面我将通过a.js.b.js和c.js三个 ...

  4. Qt 设置图标的三种方式

    Qt 设置软件窗口图标有三种方式: 一.通过资源文件,设置图标 this->setWindowIcon(QIcon(":/logo.ico")); 二.通过 pro 文件,设 ...

  5. 踩坑指南:入门OpenTenBase之部署篇

    引言 OpenTenBase 企业级分布式HTAP开源数据库,具备高扩展性.商业数据库语法兼容.分布式HTAP引擎.多级容灾和多维度资源隔离等能力,成功应用在金融.医疗.航天等行业的核心业务系统. 这 ...

  6. android 当在github下载一个android项目后

    前言 github是一个很好的社区,有时候呢,我们从android下载项目下来无法build,有很多原因,比如说库不见了,或者说自己没有安装过支持的api版本等,会遇到各种问题. 在此总结一下. 问题 ...

  7. 整理ionic 系列——页面生命周期

    前言 这是整理ionic的开篇,ionic 就不多介绍了,开发混合app的. 正文 Event Desc ionViewDidLoad 当页面加载的时候触发,仅在页面创建的时候触发一次,如果被缓存了, ...

  8. IIS 出现405

    前言 在一次配置服务器中,出现一个问题,那就是使用put和delete 出现405. 当时我蒙了,调试的时候好好的,部署405. 原因是put和delete是非简单请求,也就是说非安全请求了. 这时候 ...

  9. 基于ChatGPT打造安全脚本工具流程

    前言 以前想要打造一款自己的工具,想法挺好实际上是难以实现,第一不懂代码的构造,只有一些工具脚本构造思路,第二总是像重复造轮子这种繁琐枯燥工作,抄抄改改搞不清楚逻辑,想打造一款符合自己工作的自定义的脚 ...

  10. warmup预热学习率

    学习率是神经网络训练中最重要的超参数之一,针对学习率的优化方式很多,Warmup是其中的一种 (一).什么是Warmup?Warmup是在ResNet论文中提到的一种学习率预热的方法,它在训练开始的时 ...