当前位置: 首页 > news >正文

用动画的方式画出任意的路径(直线、曲线、折现)

WPF/UWP 中提供的 Path 类可以为我们绘制几乎所有可能的矢量图形。但是,如果这些矢量图形可以以动画的形式播放出来,那将可以得到非常炫酷的演示效果。


我用 Blend 画了我的名字:

walterlv

<Canvas x:Name="DisplayCanvas" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2">
    <FrameworkElement.Resources>
        <Style TargetType="Path">
            <Setter Property="Stretch" Value="None"/>
            <Setter Property="Stroke" Value="#FF1B6CB0"/>
            <Setter Property="StrokeThickness" Value="4"/>
        </Style>
    </FrameworkElement.Resources>
    <Path x:Name="w"   Data="M501.5,309.22 L510.5,356.22 524,324.72 536,355.72 546,306.22"/>
    <Path x:Name="a"   Data="M588.5,316.22 C588.5,316.22 561.5,308.72 558,334.72 554.5,360.72 579.5,369.21978 588,357.71985 596.5,346.21993 587.00002,315.22013 588.99999,310.22011 590.49998,326.72017 589.50007,359.22028 597.99998,359.22028"/>
    <Path x:Name="l1"  Data="M613.5,283.22 C613.5,283.22 607,372.22 623.5,357.22"/>
    <Path x:Name="t_1" Data="M635.5,317.72 L656.5,316.22"/>
    <Path x:Name="t_2" Data="M644,285.72 C644,285.72 642.5,334.72 644,345.72 645.5,356.72 657.99343,366.72 661.99155,342.72"/>
    <Path x:Name="e"   Data="M678.5,329.72 L711.5,327.72 C711.5,327.72 711,306.22 692,307.72 673,309.22 677,325.72 677,336.22 677,346.71999 685.99986,355.21999 692.49989,353.71999 698.99993,352.21999 709.49999,349.22025 709.99999,343.72022"/>
    <Path x:Name="r"   Data="M725.5,306.72 C740,309.22 733.5,336.22 733.5,344.72 735.5,326.22 726.99993,300.72 763.49829,307.22"/>
    <Path x:Name="l2"  Data="M786,281.22 C786,281.22 769,372.22 789.5,362.72"/>
    <Path x:Name="v"   Data="M803,308.22 L817,358.22 835.5,310.22"/>
</Canvas>

然后将它做成了动画:

动画绘制的路径

而要做到这一点,我们只需要关心 Path 的两个属性即可:

  • StrokeDashArray
  • StrokeDashOffset

StrokeDashArray 是一个包含有很多个 double 的浮点数集合,决定了虚线虚实的变化趋势;StrokeDashOffset 是给这个变化趋势添加一个偏移量。

如果一条直线其长度为 100,粗细为 1,StrokeDashArray="5,5" 表示这段直线用虚线表示绘制;一开始的 5 长度绘制,接下来 5 长度不绘制,再接下来 5 长度绘制,依此类推。在这种情况下,我们再设置 StrokeDashOffset="1",则将虚实的变化延后 1 个长度,即一开始空出 1 长度不绘制后,才接着 5 长度绘制。

于是,如果我们设置 StrokeDashArray="100,100",那么意味着一开始整条线都绘制,随后在看不见的线条的后面一倍长度上不绘制。我们设置 StrokeDashOffset="100" 则意味着将这个绘制整体延后 100 长度,也就是完全看不见。当 StrokeDashOffset 设置成中间值的时候,这跟线条只会绘制一部分。

于是我们的思路是:

  • 设置 StrokeDashArray,使其虚实部分都等于线的长度
  • 动画设置 StrokeDashOffset,使其从长度变化到 0

这是为此制作的动画 XAML:

<CubicEase x:Key="EasingFunction.DrawLine" EasingMode="EaseOut"/>
<Storyboard x:Key="Storyboard.DrawName">
    <DoubleAnimation Storyboard.TargetName="w" Storyboard.TargetProperty="StrokeDashOffset" To="0" BeginTime="0:0:0" Duration="0:0:1" EasingFunction="{StaticResource EasingFunction.DrawLine}"/>
    <DoubleAnimation Storyboard.TargetName="a" Storyboard.TargetProperty="StrokeDashOffset" To="0" BeginTime="0:0:1" Duration="0:0:1" EasingFunction="{StaticResource EasingFunction.DrawLine}"/>
    <DoubleAnimation Storyboard.TargetName="l1" Storyboard.TargetProperty="StrokeDashOffset" To="0" BeginTime="0:0:2" Duration="0:0:1" EasingFunction="{StaticResource EasingFunction.DrawLine}"/>
    <DoubleAnimation Storyboard.TargetName="t_1" Storyboard.TargetProperty="StrokeDashOffset" To="0" BeginTime="0:0:3" Duration="0:0:0.4" EasingFunction="{StaticResource EasingFunction.DrawLine}"/>
    <DoubleAnimation Storyboard.TargetName="t_2" Storyboard.TargetProperty="StrokeDashOffset" To="0" BeginTime="0:0:3.4" Duration="0:0:0.6" EasingFunction="{StaticResource EasingFunction.DrawLine}"/>
    <DoubleAnimation Storyboard.TargetName="e" Storyboard.TargetProperty="StrokeDashOffset" To="0" BeginTime="0:0:4" Duration="0:0:1" EasingFunction="{StaticResource EasingFunction.DrawLine}"/>
    <DoubleAnimation Storyboard.TargetName="r" Storyboard.TargetProperty="StrokeDashOffset" To="0" BeginTime="0:0:5" Duration="0:0:1" EasingFunction="{StaticResource EasingFunction.DrawLine}"/>
    <DoubleAnimation Storyboard.TargetName="l2" Storyboard.TargetProperty="StrokeDashOffset" To="0" BeginTime="0:0:6" Duration="0:0:1" EasingFunction="{StaticResource EasingFunction.DrawLine}"/>
    <DoubleAnimation Storyboard.TargetName="v" Storyboard.TargetProperty="StrokeDashOffset" To="0" BeginTime="0:0:7" Duration="0:0:1" EasingFunction="{StaticResource EasingFunction.DrawLine}"/>
</Storyboard>

于是我们便可以在 C# 代码中初始化那些 XAML 里算不出来的值(Path 中线的长度):

private Storyboard DrawLineStoryboard => (Storyboard) FindResource("Storyboard.DrawName");

private async void OnLoaded(object sender, RoutedEventArgs args)
{
    for (var i = 0; i < DrawLineStoryboard.Children.Count; i++)
    {
        InitializePathAndItsAnimation((Path) DisplayCanvas.Children[i], (DoubleAnimation) DrawLineStoryboard.Children[i]);
    }
    DrawLineStoryboard.Begin();
}

private void InitializePathAndItsAnimation(System.Windows.Shapes.Path path, DoubleAnimation animation)
{
    var length = path.Data.GetProximateLength() / path.StrokeThickness;
    path.StrokeDashOffset = length;
    path.StrokeDashArray = new DoubleCollection(new[] {length, length});
    animation.From = length;
}

上述代码中存在一个线长度的估值算法,我们的策略是用多边形近似:

public static class GeometryExtensions
{
    public static double GetProximateLength(this Geometry geometry)
    {
        var path = geometry.GetFlattenedPathGeometry();
        var length = 0.0;
        foreach (var figure in path.Figures)
        {
            var start = figure.StartPoint;
            foreach (var segment in figure.Segments)
            {
                if (segment is PolyLineSegment polyLine)
                {
                    // 一般的路径会转换成折线。
                    foreach (var point in polyLine.Points)
                    {
                        length += ProximateDistance(start, point);
                        start = point;
                    }
                }
                else if (segment is LineSegment line)
                {
                    // 少部分真的是线段的路径会转换成线段。
                    length += ProximateDistance(start, line.Point);
                    start = line.Point;
                }
            }
        }
        return length;

        double ProximateDistance(Point p1, Point p2)
        {
            return Math.Sqrt(Math.Pow(p1.X - p2.X, 2) + Math.Pow(p1.Y - p2.Y, 2));
        }
    }
}

参考资料

  • SVG技术入门:如何画出一条会动的线 – WEB骇客
  • c# - Getting Geometry length - Stack Overflow

相关文章:

  • 使 WPF 支持触摸板的横向滚动
  • NullReferenceException,就不应该存在!
  • 当我们使用 MVVM 模式时,我们究竟在每一层里做些什么?
  • 分享一个算法,计算能在任何背景色上清晰显示的前景色
  • WPF 绘制对齐像素的清晰显示的线条
  • 让 ScrollViewer 的滚动带上动画
  • UI 设计中的视觉无障碍设计
  • 为什么委托的减法(- 或 -=)可能出现非预期的结果?(Delegate Subtraction Has Unpredictable Result)
  • 将美化进行到底,使用 Oh My Posh 把 PowerShell 做成 oh-my-zsh 的样子
  • 实现一个 WPF 版本的 ConnectedAnimation
  • C#/.NET 中的契约
  • WPF 自定义键盘焦点样式(FocusVisualStyle)
  • 异步任务中的重新进入(Reentrancy)
  • 迫不及待地体验了一把 C#8.0 中的可空引用类型(Nullable Reference)
  • C#/.NET 匿名函数会捕获变量,并延长对象的生命周期
  • -------------------- 第二讲-------- 第一节------在此给出链表的基本操作
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • Java Agent 学习笔记
  • Java深入 - 深入理解Java集合
  • Material Design
  • npx命令介绍
  • Python打包系统简单入门
  • Python学习之路16-使用API
  • ucore操作系统实验笔记 - 重新理解中断
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 解决iview多表头动态更改列元素发生的错误
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • (01)ORB-SLAM2源码无死角解析-(66) BA优化(g2o)→闭环线程:Optimizer::GlobalBundleAdjustemnt→全局优化
  • (4)事件处理——(7)简单事件(Simple events)
  • (Pytorch框架)神经网络输出维度调试,做出我们自己的网络来!!(详细教程~)
  • (Redis使用系列) Springboot 在redis中使用BloomFilter布隆过滤器机制 六
  • (独孤九剑)--文件系统
  • (多级缓存)缓存同步
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (附源码)计算机毕业设计SSM智能化管理的仓库管理
  • (十八)SpringBoot之发送QQ邮件
  • (转)fock函数详解
  • (转载)(官方)UE4--图像编程----着色器开发
  • (转载)Google Chrome调试JS
  • *上位机的定义
  • .Net CF下精确的计时器
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET Core/Framework 创建委托以大幅度提高反射调用的性能
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性
  • .NET Remoting学习笔记(三)信道
  • .net流程开发平台的一些难点(1)
  • .net中应用SQL缓存(实例使用)
  • [ 隧道技术 ] 反弹shell的集中常见方式(二)bash反弹shell
  • [2019.3.20]BZOJ4573 [Zjoi2016]大森林
  • [BZOJ1178][Apio2009]CONVENTION会议中心
  • [C#]猫叫人醒老鼠跑 C#的委托及事件
  • [C++] Boost智能指针——boost::scoped_ptr(使用及原理分析)
  • [git] windows系统安装git教程和配置