像在画布上绘制一样对路径进行动画处理

问题描述 投票:0回答:4

我是 WPF 的新手,请指导我解决这个问题的正确方向。

我构建了一个 WPF 应用程序,其中包含路线图视图控件的所有功能。 IE。路线图可以使用鼠标、键盘和提供的控件进行放大/缩小、向各个方向平移。我已将道路映射为使用 Expression Blend 绘制的路径。

目前我正在寻找一种方法来为选定的道路设置动画,就像它是用铅笔/钢笔/记号笔绘制的一样。这可能吗?到目前为止,我已经能够为路径的不透明度和颜色设置动画。我已经搜索了很多这个功能但没有运气。可能是我没有搜索正确的术语。我希望你们中的某个人能够对此事有所了解。

提前致谢。抱歉,如果我听起来很疯狂:)编程是我疯狂的方式:D

wpf animation path draw
4个回答
3
投票

TL;DR:我们利用

PointAnimationUsingPath
。我们沿着路径对一个点进行动画处理,并在该点移动时构建一个
Clip
几何体。


完整答案:

我首先在

Path
中绘制样本
Grid
以进行演示。将实际的
Path
数据放入资源中,因为我们稍后会重复使用它。

<Grid>
    <Grid.Resources>
        <PathGeometry x:Key="path">
            <PathFigure>
                <BezierSegment Point1="10 30" Point2="100 100" Point3="200 10" />
            </PathFigure>
        </PathGeometry>
    </Grid.Resources>
    <Path x:Name="myPath" StrokeThickness="5" Stroke="Black" Data="{StaticResource path}" />
</Grid>

然后我为

Clip
定义一个空的
Path
几何图形:

<Path.Clip>
    <GeometryGroup x:Name="geometryGroup" FillRule="Nonzero"/>
</Path.Clip>

到目前为止,

Path
消失了,因为它被剪切到一个空的几何体中。我们剩下要做的就是逐步将点添加回该剪切几何体中以显示
Path
。 为此,我们需要一个动画对象。我建议创建一个
FrameworkPoint
进行演示:

public class FrameworkPoint : FrameworkElement {
    public static DependencyProperty CenterProperty = DependencyProperty.RegisterAttached("Center", typeof(Point), typeof(FrameworkPoint));
    public Point Center { get => (Point)GetValue(CenterProperty); set => SetValue(CenterProperty, value); }

    public event Action<Point> CoordinatesChanged;

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) {
        base.OnPropertyChanged(e);
        if (e.Property == CenterProperty) {
            CoordinatesChanged?.Invoke(Center);
        }
    }
}

这是一个只有一个类型为

Point
的属性的对象,并且该属性是 animable。让我们在
Grid
中添加(不可见)点,并在
Path
上为其设置动画:

<local:FrameworkPoint x:Name="myPoint">
    <local:FrameworkPoint.Triggers>
        <EventTrigger RoutedEvent="Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <PointAnimationUsingPath Duration="00:00:10"
                                             Storyboard.TargetProperty="Center"
                                             PathGeometry="{StaticResource path}"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </local:FrameworkPoint.Triggers>
</local:FrameworkPoint>

启动时,

FrameworkPoint
将在指示的时间(10 秒)内无形地跟随
Path
。剩下要做的就是随着点的移动构建我们的
Clip

public partial class MainWindow : Window {
    public MainWindow() {
        InitializeComponent();
        myPoint.CoordinatesChanged += MyPoint_CoordinatesChanged;
    }

    private void MyPoint_CoordinatesChanged(Point obj) {
        geometryGroup.Children.Add(new EllipseGeometry(obj, 5, 5));
    }
}

这不会为快速动画提供完美的结果,因为采样不够好,但它可以给你一些想法!


1
投票

我不太确定这是否是您正在寻找的,但我会尝试一下。

动画会有点复杂。它实际上是一系列动画,路径中的每个点减去第一个点都有一个动画。您可能希望从源路径一次向动画路径添加一个点。每次添加点时,该点都会从前一个点开始,然后移动到所需的点。动画会随着时间的推移移动新添加的点,从而给出该片段被“绘制”的效果。当该动画完成时,您将迭代到下一个点并开始下一个动画。


1
投票

我不想在评论中出现,这是一篇很棒的文章:

http://social.msdn.microsoft.com/Forums/en/wpf/thread/19a7bd4b-cf28-4b31-a329-a5f58b9ec374

这是 Charles Petzold 对这个问题的看法:

http://www.charlespetzold.com/blog/2006/08/150351.html


0
投票

我解决这个问题的方法如下:我创建了一个新的 FrameworkElement 并添加了一个属性,它是要绘制的路径的百分比。然后我可以为这个属性设置动画。

窗口的XAML:

<Window x:Class="Algonim.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:demo="clr-namespace:Demo"
        Background="DarkSlateGray" ResizeMode="NoResize" WindowStyle="None"
        mc:Ignorable="d" Foreground="White" FontSize="16" FontFamily="Excalifont" WindowStartupLocation="CenterScreen"
        Title="MainWindow" SizeToContent="WidthAndHeight">
    <Window.Style>
        <Style TargetType="{x:Type Window}">
            <Setter Property="WindowChrome.WindowChrome">
                <Setter.Value>
                    <WindowChrome CaptionHeight="32" UseAeroCaptionButtons="False" ResizeBorderThickness="0" GlassFrameThickness="0"
                                  CornerRadius="0" NonClientFrameEdges="None"/>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Style>
    <Canvas Width="300" Height="300" Margin="24">
        <demo:AnimatedPath x:Name="path" Stroke="White" StrokeThickness="2" Data="F1 M 236.748,300.578C 233.384,300.578 230.142,299.787 227.021,298.207C 223.131,295.127 218.085,288.784 211.884,279.179L 209.939,275.289C 209.656,271.925 210.426,267.001 212.249,260.517C 214.073,254.032 214.721,249.088 214.195,245.684C 214.195,243.09 212.898,238.815 210.304,232.857C 208.764,231.317 207.852,230.547 207.568,230.547L 203.678,228.602C 198.247,229.899 186.646,232.037 168.875,235.015C 151.104,237.994 138.855,240.78 132.128,243.374C 127.427,244.671 123.658,245.319 120.821,245.319C 117.7,245.319 114.195,244.792 110.304,243.739L 107.994,243.739C 106.94,244.549 103.566,247.741 97.8723,253.313C 92.1783,258.886 86.7984,264.073 81.7325,268.875C 76.6667,273.678 73.2219,276.727 71.3982,278.024C 68.5613,280.091 63.9007,283.262 57.4164,287.538C 50.9321,291.814 46.2513,295.127 43.3739,297.477C 34.2959,297.477 29.7569,295.005 29.7569,290.061C 28.2168,281.51 31.3374,272.553 39.1186,263.192L 76.079,228.967C 88.5208,214.701 102.391,197.062 117.69,176.049C 132.989,155.036 144.792,139.747 153.1,130.182C 155.694,126.292 159.98,119.808 165.957,110.73C 171.935,101.652 176.282,94.7721 178.997,90.0913C 181.712,85.4104 185.147,79.3111 189.301,71.7934C 193.455,64.2757 196.697,57.0821 199.027,50.2128C 201.358,43.3435 203.293,36.1399 204.833,28.6019C 204.833,28.3587 205.42,25.9677 206.596,21.4287C 207.771,16.8897 208.237,14.3567 207.994,13.8299C 213.951,5.52188 220.03,1.24631 226.231,1.00314L 230.912,0.577606C 236.099,0.577606 240.507,2.40134 244.134,6.04874C 247.761,9.69614 249.453,13.7083 249.21,18.0852L 246.109,59.3618C 245.826,63.4955 245.491,68.9362 245.106,75.684C 244.721,82.4317 244.397,87.8116 244.134,91.8238C 243.87,95.8359 243.617,98.7539 243.374,100.578C 243.617,113.546 243.678,123.273 243.556,129.757C 243.435,136.241 243.181,145.907 242.796,158.754C 242.411,171.601 242.219,181.388 242.219,188.116C 244.813,190.709 247.913,192.006 251.52,192.006C 252.047,192.006 253.475,192.199 255.805,192.584C 258.136,192.969 259.949,193.293 261.246,193.556C 262.543,193.82 264.043,194.215 265.745,194.742C 267.447,195.269 268.875,196.049 270.03,197.082C 271.185,198.116 272.148,199.402 272.918,200.942C 273.202,201.469 273.272,202.513 273.131,204.073C 272.989,205.633 272.796,206.677 272.553,207.204C 269.716,209.797 262.199,214.073 250,220.03C 248.946,220.274 247.913,220.922 246.9,221.976C 245.603,225.623 246.12,232.239 248.45,241.824C 250.78,251.408 251.945,256.849 251.945,258.146C 252.188,260.496 252.705,264.073 253.495,268.875C 254.286,273.678 254.802,277.376 255.046,279.97C 255.289,282.563 254.762,285.603 253.465,289.088C 252.168,292.573 249.838,295.613 246.474,298.207C 243.637,299.787 240.395,300.578 236.748,300.578 Z M 139.909,204.833C 141.449,204.833 144.813,204.772 150,204.651C 155.187,204.529 159.017,204.407 161.489,204.286C 163.961,204.164 167.589,203.901 172.371,203.495C 177.153,203.09 181.104,202.634 184.225,202.128C 187.345,201.621 190.851,200.851 194.742,199.818C 198.632,198.784 202.138,197.619 205.258,196.322L 207.994,194.377L 209.514,175.289L 209.514,157.416L 209.149,123.131L 209.149,105.258C 204.975,107.852 200.689,113.556 196.292,122.371C 191.895,131.185 187.872,137.275 184.225,140.638C 179.037,149.23 170.608,160.851 158.936,175.502C 147.264,190.152 140.132,199.281 137.538,202.888C 137.295,203.171 137.497,203.566 138.146,204.073C 138.794,204.58 139.382,204.833 139.909,204.833 Z " PercentToDraw="1"/>
    </Canvas>
    <Window.Triggers>
        <EventTrigger RoutedEvent="MouseDown">
            <BeginStoryboard>
                <Storyboard>
                    <DoubleAnimation Storyboard.TargetName="path" Storyboard.TargetProperty="PercentToDraw" From="0" To="1" Duration="0:0:5"/>
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Window.Triggers>
</Window>

班级:

using System.Windows;
using System.Windows.Media;

namespace Demo;

public sealed class AnimatedPath : FrameworkElement
{
    public static readonly DependencyProperty DataProperty = DependencyProperty.Register(nameof(Data), typeof(Geometry), typeof(AnimatedPath), new PropertyMetadata(null!));

    public static readonly DependencyProperty FillProperty = DependencyProperty.Register(nameof(Fill), typeof(Brush), typeof(AnimatedPath), new FrameworkPropertyMetadata(null!, FrameworkPropertyMetadataOptions.AffectsRender));

    public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(nameof(Stroke), typeof(Brush), typeof(AnimatedPath), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), FrameworkPropertyMetadataOptions.AffectsRender));

    public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(nameof(StrokeThickness), typeof(ushort), typeof(AnimatedPath), new FrameworkPropertyMetadata((ushort)0, FrameworkPropertyMetadataOptions.AffectsRender));

    public static readonly DependencyProperty PercentToDrawProperty = DependencyProperty.Register(nameof(PercentToDraw), typeof(double), typeof(AnimatedPath),
        new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender));

    public double PercentToDraw
    {
        get => (double)GetValue(PercentToDrawProperty);
        set => SetValue(PercentToDrawProperty, value);
    }

    public Geometry? Data
    {
        get => (Geometry?)GetValue(DataProperty);
        set => SetValue(DataProperty, value!);
    }

    public Brush? Fill
    {
        get => (Brush?)GetValue(FillProperty);

        set => SetValue(FillProperty, value!);
    }

    public Brush? Stroke
    {
        get => (Brush?)GetValue(StrokeProperty);

        set => SetValue(StrokeProperty, value!);
    }

    public ushort StrokeThickness
    {
        get => (ushort)GetValue(StrokeThicknessProperty);

        set => SetValue(StrokeThicknessProperty, value);
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        if (Data is null) return;

        var pen = new Pen(Stroke!, StrokeThickness);

        if (PercentToDraw >= 1)
        {
            drawingContext.DrawGeometry(Fill!, pen, Data!);
            return;
        }

        // Partial draw
        PathGeometry flattenedPathGeometry = Data.GetFlattenedPathGeometry();
        double totalLength = 0;
        Queue<double> segmentLengths = new();
        for (int i = 0; i < flattenedPathGeometry.Figures!.Count; i++)
        {
            PathFigure figure = flattenedPathGeometry.Figures![i]!;
            Point previousPoint = figure.StartPoint;
            for (int j = 0; j < figure.Segments!.Count; j++)
            {
                PathSegment segment = figure.Segments![j]!;
                switch (segment)
                {
                    case PolyLineSegment polyLine:
                        {
                            for (int k = 0; k < polyLine.Points!.Count; k++)
                            {
                                Point point = polyLine.Points![k];
                                double segmentLength = (point - previousPoint).Length;
                                totalLength += segmentLength;
                                segmentLengths.Enqueue(totalLength);
                                previousPoint = point;
                            }
                            break;
                        }

                    case LineSegment lineSegment:
                        {
                            double segmentLength = (lineSegment.Point - previousPoint).Length;
                            totalLength += segmentLength;
                            segmentLengths.Enqueue(totalLength);
                            previousPoint = lineSegment.Point;
                            break;
                        }
                }
            }
        }

        double lengthToDraw = totalLength * PercentToDraw;
        foreach (PathFigure figure in flattenedPathGeometry.Figures)
        {
            Point previousPoint = figure.StartPoint;
            for (int j = 0; j < figure.Segments!.Count; j++)
            {
                PathSegment segment = figure.Segments![j]!;
                switch (segment)
                {
                    case PolyLineSegment polyLine:
                        {
                            for (int k = 0; k < polyLine.Points!.Count; k++)
                            {
                                if (segmentLengths.Dequeue() > lengthToDraw) return;
                                Point point = polyLine.Points![k];
                                drawingContext.DrawLine(pen, previousPoint, point);
                                previousPoint = point;
                            }
                        }
                        break;

                    case LineSegment lineSegment:
                        {
                            if (segmentLengths.Dequeue() > segmentLengths.Count) return;
                            drawingContext.DrawLine(pen, previousPoint, lineSegment.Point);
                            previousPoint = lineSegment.Point;
                        }
                        break;
                }
            }
        }
    }
}

OnRender()
方法中,我首先通过使用
GetFlattenedPathGeometry()
将其转换为扁平版本来近似路径的总长度。同时,我建立了一个查找表,当我绘制到所要求的长度时,它可以让我停止。

Animate path demo

© www.soinside.com 2019 - 2024. All rights reserved.