1. 程式人生 > >用動畫的方式畫出任意的路徑(直線、曲線、折現)

用動畫的方式畫出任意的路徑(直線、曲線、折現)

pub length 資料 new object n) 整體 for duration

原文:用動畫的方式畫出任意的路徑(直線、曲線、折現)

版權聲明:本作品采用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名呂毅(包含鏈接:http://blog.csdn.net/wpwalter/),不得用於商業目的,基於本文修改後的作品務必以相同的許可發布。如有任何疑問,請與我聯系([email protected])。 https://blog.csdn.net/WPwalter/article/details/78619867

WPF/UWP 中提供的 Path 類可以為我們繪制幾乎所有可能的矢量圖形。但是,如果這些矢量圖形可以以動畫的形式播放出來,那將可以得到非常炫酷的演示效果。


我用 Blend 畫了我的名字:

技術分享圖片

<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

用動畫的方式畫出任意的路徑(直線、曲線、折現)