WPF使用Shape实现复杂线条动画

news/2024/9/21 10:38:09

看到巧用 CSS/SVG 实现复杂线条光效动画的文章,便也想尝试用WPF的Shape配合动画实现同样的效果。ChokCoco大佬的文章中介绍了基于SVG的线条动画效果和通过角向渐变配合 MASK 实现渐变线条两种方式。WPF中的Shape与SVG非常相似,因此这种方式也很容易实现。但WPF中仅有的两种渐变画刷不包含角向渐变,本文使用了另外两种方式实现同样的效果。

在Avalonia的API文档中有看到ConicGradientBrush,应该可以用角向渐变的方式来实现。

首先看一下三种方式实现的效果(录制的gif中颜色存在一些偏差,动画有些卡顿,实际效果要好一些):
image

基于Polyline的线条动画效果

这种方式也是利用StrokeDashArray实现虚线样式,然后通过动画设置StrokeDashOffset来实现动画。首先,用Polyline绘制一段折线:

<Polyline Points="240 20 140 20 140 100 0 100" Stroke="#ddd" />

这样,我们就得到一条这样的折线:
image

接下来,利用StrokeDashArray实现与上边折线相同路径的虚线(点划线):

<Polyline Points="240 20 140 20 140 100 0 100" Stroke="red"
StrokeDashArray="20 30" />

image

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" />

image
接下来就是借助StrokeDashOffset来实现动画。

<GridGrid.Row="0"Grid.Column="0"Margin="5"><Polyline Points="240 20 140 20 140 100 0 100" Stroke="#ddd" /><PolylinePoints="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"><DoubleAnimationFrom="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" />
<Polylinex:Name="polyline1"Points="260 20 240 20"Stroke="{StaticResource linearBrush}" />
<Polylinex:Name="polyline2"Points="140 0 140 20"Stroke="{StaticResource linearBrush}" />
<Polylinex:Name="polyline3"Points="160 100 140 100"Stroke="{StaticResource linearBrush}" />

image

这里有个细节需要注意,第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>

image

这样看效果并不明显,接下来就是需要用一个形状把完成动画的线段遮挡起来:

<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" />

image

这样基本实现了渐变色线条的动画效果,但终究不够优雅。

基于等腰三角形的动画

上一种方法中,在拐角处由两条线段配合的动画实现的效果,一条线段移出,另一条移入,连接起来刚好是个等腰直角三角形。
image

然后用线性渐变色填充三角形就可以实现移出的线段颜色和移入部分颜色相同。

<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>
<Polygonx: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" /><Polygonx: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>

image

小结

基于Polyline的线条StrokeDashOffset的方式最为灵活简洁,不仅适用于直角折线,还适用于各种曲线。如果把此处的Polyline换成一个Ellipse,就可以实现简单的转圈圈等待的动效,但其不足在于线条样式美化空间有限。

基于多条线段的动画可以美化线条,但只适用于Polyline或者直线组成的Path,一旦存在曲线就不适用了。

基于等腰三角形的动画可以看做是基于多条线段的动画的一种特殊场景,局限性较大,仅适用于带直角的折线。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/31704.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

maven打包失败

问题: 用maven命令打包java应用jar包时打包失败,显示错误“找不到符号”,但是具体的代码中没有找到错误 解决方法: 如果打包的应用为子应用,需要到父应用中打包

EBS 可保留量(Quantity Available To Reserve) 异常 -- 子库间的转移

1、出现可保留量数量为0或者不不相等的情况 2、查看子库状态的生效状态 3、从新跑请求:保留接口管理器4、最后可用量就出来了本文来自博客园,作者:Iven_lin,转载请注明原文链接:https://www.cnblogs.com/ivenlin/p/18192660

IceRPC之调用管道Invocation pipeline与传出请求Outgoing request-快乐的RPC

作者引言 .Net 8.0 下的新RPC很高兴啊,我们来到了IceRPC之调用管道 Invocation pipeline与传出请求 Outgoing request->快乐的RPC, 基础引导,让自已不在迷茫,快乐的畅游世界。调用管道 Invocation pipeline了解如何发送请求requests和接收响应responses。定义 发送请求并…

LCD屏显示练习【二】

本次习题主要是了解开机动画与触屏切换界面的相关原理,为后续的项目实现打下基础目录题目题目分析思路解析知识点涉及代码展示优化思考问题一:观察界面切换效果,可明显观察到界面切换时有明显的刷新效果,有点影响使用效果问题二:图片的按键位置不能相近或者重合,否则有误…

.net DataGirdView 通过列索引修改单元格字体

场景是这样、我需要DataGirdView某几列数量大于0字体就变成蓝色,某几列超过标准值字体就变成红色具体列名属性void InitCols(){var col = _DataGridView.BuildCol<DataGridViewTextBoxColumn>(dgvDetail, "OrderNo", " 工单号");col.Width = 125;//…

原型设计软件对比以及介绍项目EventSphere原型设计方案

一、介绍项目的原型设计方案 项目名称:EventSphere EventSphere 旨在建立一个全面的、多维度的学生活动发布平台,涉及不仅是面对面的实体活动,还包括虚拟活动的体验,如虚拟现实(VR)活动。该平台计划提供以下功能:用户注册与登录 :允许学生、教师和活动组织者创建账户并登…

初识汇编-第一篇

计算机的组成-cpu的模型

dotnet 后台线程发送 X11 窗口消息

本文将告诉大家如何在 dotnet 里面的后台线程向自己进程内的窗口发送消息核心是通过 XSendEvent 发送消息,发送消息想要有反应需要另开 XOpenDisplay 获取 display 对象,最后再将其关闭才能发送出去 核心代码如下 _ = Task.Run(async () => {while (true){await Task.Dela…