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

WPF中的PathAnimation(路径动画)
                                                               周银辉

在WPF中我们可以指定元素按照某一路径运动而形成动画,这称之为路径动画,在SDK中你会发现以[Type]AnimationUsingPath这种形式命名的类,它们就是用来创建路径动画的,其中[Type]表示一系列类型比如double,point等,这与DoubleAnimation等的命名方式一致。
为对路径动画有一个大体上的认识,你可以粘贴下面的代码到XamlPad:

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    
    x:Name="Window"
    Title="Window1"
    Width="640" Height="480">

    <Window.Resources>
        <Storyboard x:Key="Timeline1" RepeatBehavior = "Forever" AutoReverse="True">
            <DoubleAnimationUsingPath BeginTime="00:00:00" Duration="00:00:02" Storyboard.TargetName="button" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)" Source="X">
                <DoubleAnimationUsingPath.PathGeometry>
                    <PathGeometry Figures="M-68,149.00022 C-39.695816,109.3744 -17.383496,83.397528 39,74.000287 C73.433618,68.261356 91.198465,71.210009 125,89.000273 C157.86626,106.29829 181.01028,123.79991 204,155.00021 C213.7418,168.22121 224.03351,180.96708 234,194.00017 C246.07551,209.79121 257.36571,220.41957 275,231.00014 C285.57406,237.34456 295.83653,242.84775 307,248.00012 C317.8183,252.99318 324.46505,252.89547 337,252.00012 C362.92461,250.14836 384.46844,240.41978 404,225.00014"/>
                </DoubleAnimationUsingPath.PathGeometry>
            </DoubleAnimationUsingPath>
            <DoubleAnimationUsingPath BeginTime="00:00:00" Duration="00:00:02" Storyboard.TargetName="button" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)" Source="Y">
                <DoubleAnimationUsingPath.PathGeometry>
                    <PathGeometry Figures="M-68,149.00022 C-39.695816,109.3744 -17.383496,83.397528 39,74.000287 C73.433618,68.261356 91.198465,71.210009 125,89.000273 C157.86626,106.29829 181.01028,123.79991 204,155.00021 C213.7418,168.22121 224.03351,180.96708 234,194.00017 C246.07551,209.79121 257.36571,220.41957 275,231.00014 C285.57406,237.34456 295.83653,242.84775 307,248.00012 C317.8183,252.99318 324.46505,252.89547 337,252.00012 C362.92461,250.14836 384.46844,240.41978 404,225.00014"/>
                </DoubleAnimationUsingPath.PathGeometry>
            </DoubleAnimationUsingPath>
        </Storyboard>
    </Window.Resources>
    <Window.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard Storyboard="{StaticResource Timeline1}"/>
        </EventTrigger>
    </Window.Triggers>

    <Grid x:Name="LayoutRoot">
        <Path Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000" Margin="63.5,128.847,87.5,133.025" Data="M64,207 C92.304184,167.37414 114.6165,141.39725 171,132 205.43362,126.26106 223.19847,129.20972 257,147 289.86626,164.29803 313.01028,181.79967 336,213 345.7418,226.22101 356.03351,238.96689 366,252 378.07551,267.79105 389.36571,278.41942 407,289 417.57406,295.34443 427.83653,300.84763 439,306 449.8183,310.99306 456.46505,310.89535 469,310 494.92461,308.14824 516.46844,298.41966 536,283"/>
        <Button RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Left" Margin="82,30,0,0" x:Name="button" VerticalAlignment="Top" Width="100" Height="56" Content="Button">
            <Button.RenderTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="1" ScaleY="1"/>
                    <SkewTransform AngleX="0" AngleY="0"/>
                    <RotateTransform Angle="0"/>
                    <TranslateTransform X="0" Y="0"/>
                </TransformGroup>
            </Button.RenderTransform>
        </Button>
    </Grid>
</Window>

你可以得到如图所示的一个窗体:

窗体上的按钮沿着曲线来回的运动(按钮的中心始终与曲线相切)。

1,使用Microsoft Expression Blend工具为我们自动生成路径动画
打造路径动画的最简单的方式是使用 Microsoft Expression Blend工具来自动生成,当然这自然会失去灵活性。
第一步:创建动画对象(比如上图中的那个Button)

第二步:创建动画路径(比如一条曲线,一个圆)

记得将路径的逻辑树层次上调整得比刚才那个按钮低

第三步:将路径转化为MotionPath
选中逻辑树上的[Path]对象,单击菜单栏的Object,选择Path子项,在选择Convert To MotionPath,则会弹出如下窗口:
其要求你选择MotionPath的目标,也就是路径动画的目标,我们这里选择我们的button按钮
单击OK,其会自动为你生成Timeline(此后的操作就与打造普通Timeline 的操作一样了,其实此时该路径动画已经生成,你可以单击Timeline录制旁边的“播放”按钮来观看效果了,或直接F5)

2,使用代码来打造更灵活的路径动画
我更喜欢使用手写代码的方式来做更多的事情,这让事情变得更容易控制,同时也更具有灵活性。
以DoubleAnimationUsingPath为例,我们逐步讲解一下如何使用代码来打造上面的示例。
为了让对象沿着曲线运动,我们通过改变一个TranslateTransform的X与Y值来做到,并将该TranslateTransform指定给按钮的RenderTransform。
假设我们动画中的路径(也就是示例中的那条曲线)为path1,动画的对象为border1,如果你有过编写WPF动画代码的经验,那么你可以很轻松地编写出以下代码:

            DoubleAnimationUsingPath animationX = new DoubleAnimationUsingPath();
            animationX.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationX.Source = PathAnimationSource.X;
            animationX.Duration = new Duration(TimeSpan.FromSeconds(2));

            DoubleAnimationUsingPath animationY = new DoubleAnimationUsingPath();
            animationY.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationY.Source = PathAnimationSource.Y;
            animationY.Duration = animationX.Duration;

其中

animationX.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();

指定了我们的animationX所遵照的路径为path1

animationX.Source = PathAnimationSource.X;

指定了animationX将遵照路径的X值的变化而变化,其中PathAnimationSource枚举存在三个值,分别是X,Y与Angle,在沿着路径旋转的动画中将会用到Angle值。
将代码补充完整后如下:

           TranslateTransform translate = new TranslateTransform();
            this.border1.RenderTransform = translate;

            NameScope.SetNameScope(this, new NameScope());
            this.RegisterName("translate", translate);

            DoubleAnimationUsingPath animationX = new DoubleAnimationUsingPath();
            animationX.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationX.Source = PathAnimationSource.X;
            animationX.Duration = new Duration(TimeSpan.FromSeconds(2));

            DoubleAnimationUsingPath animationY = new DoubleAnimationUsingPath();
            animationY.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationY.Source = PathAnimationSource.Y;
            animationY.Duration = animationX.Duration;


            Storyboard story = new Storyboard();
            story.RepeatBehavior = RepeatBehavior.Forever;
            story.AutoReverse = true;
            story.Children.Add(animationX);
            story.Children.Add(animationY);
            Storyboard.SetTargetName(animationX, "translate");
            Storyboard.SetTargetName(animationY, "translate");
            Storyboard.SetTargetProperty(animationX, new PropertyPath(TranslateTransform.XProperty));
            Storyboard.SetTargetProperty(animationY, new PropertyPath(TranslateTransform.YProperty));

            story.Begin(this);

到此为止,我们可以说已经打造了一个路径动画,效果如下图

但不难发现,图中方块运动的路径与曲线并为完全重合,而是存在X与Y方向上的平移。这是一个很让人头疼的问题,但是我发现,只要在动画播放之前,将方块的中心位置设置为与动画所在容器的(0,0)位置向重合,那么该问题就可以被避免。
修改后的代码:

            Canvas.SetTop(this.border1, -this.border1.ActualHeight / 2);
            Canvas.SetLeft(this.border1, -this.border1.ActualWidth / 2);

            TranslateTransform translate = new TranslateTransform();
            this.border1.RenderTransform = translate;

            NameScope.SetNameScope(this, new NameScope());
            this.RegisterName("translate", translate);

            DoubleAnimationUsingPath animationX = new DoubleAnimationUsingPath();
            animationX.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationX.Source = PathAnimationSource.X;
            animationX.Duration = new Duration(TimeSpan.FromSeconds(2));

            DoubleAnimationUsingPath animationY = new DoubleAnimationUsingPath();
            animationY.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationY.Source = PathAnimationSource.Y;
            animationY.Duration = animationX.Duration;


            Storyboard story = new Storyboard();
            story.RepeatBehavior = RepeatBehavior.Forever;
            story.AutoReverse = true;
            story.Children.Add(animationX);
            story.Children.Add(animationY);
            Storyboard.SetTargetName(animationX, "translate");
            Storyboard.SetTargetName(animationY, "translate");
            Storyboard.SetTargetProperty(animationX, new PropertyPath(TranslateTransform.XProperty));
            Storyboard.SetTargetProperty(animationY, new PropertyPath(TranslateTransform.YProperty));

            story.Begin(this);

然后,运行效果如下:


刚才已经提到,PathAnimationSource枚举拥有第三个值Angle,它是用来使我们的动画对象沿着路径旋转的,旋转对象与移动类似,但需要注意的是:
(1)旋转时对象的中心点始终与路径相切,那么你需要注意旋转时对象的中心在哪,比如上面的方块的中心点在其长宽的50%处,那么我们设置其RenderTransformOrigin为(0.5,0.5)

this.border1.RenderTransformOrigin = new Point(0.5, 0.5);

(2)如果我们在旋转的同时在进行其他动作,比如平移,那么我们需要注意动作叠加的顺序,先旋转再平移与先平移再旋转的效果是不同的(你可以参考WPF中的MatrixTransform),这在编码时体现在向TransformGroup中添加元素时的顺序

            TranslateTransform translate = new TranslateTransform();
            RotateTransform rotate = new RotateTransform();
            TransformGroup group = new TransformGroup();
            group.Children.Add(rotate);//先旋转
            group.Children.Add(translate);//再平移
            this.border1.RenderTransform = group;

补充完整后的代码:

            Canvas.SetTop(this.border1, -this.border1.ActualHeight / 2);
            Canvas.SetLeft(this.border1, -this.border1.ActualWidth / 2);

            this.border1.RenderTransformOrigin = new Point(0.5, 0.5);
        
            TranslateTransform translate = new TranslateTransform();
            RotateTransform rotate = new RotateTransform();
            TransformGroup group = new TransformGroup();
            group.Children.Add(rotate);//先旋转
            group.Children.Add(translate);//再平移
            this.border1.RenderTransform = group;

            NameScope.SetNameScope(this, new NameScope());
            this.RegisterName("translate", translate);
            this.RegisterName("rotate", rotate);

            DoubleAnimationUsingPath animationX = new DoubleAnimationUsingPath();
            animationX.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationX.Source = PathAnimationSource.X;
            animationX.Duration = new Duration(TimeSpan.FromSeconds(2));

            DoubleAnimationUsingPath animationY = new DoubleAnimationUsingPath();
            animationY.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationY.Source = PathAnimationSource.Y;
            animationY.Duration = animationX.Duration;

            DoubleAnimationUsingPath animationAngle = new DoubleAnimationUsingPath();
            animationAngle.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            animationAngle.Source = PathAnimationSource.Angle;
            animationAngle.Duration = animationX.Duration;

            Storyboard story = new Storyboard();
            story.RepeatBehavior = RepeatBehavior.Forever;
            story.AutoReverse = true;
            story.Children.Add(animationX);
            story.Children.Add(animationY);
            story.Children.Add(animationAngle);
            Storyboard.SetTargetName(animationX, "translate");
            Storyboard.SetTargetName(animationY, "translate");
            Storyboard.SetTargetName(animationAngle, "rotate");
            Storyboard.SetTargetProperty(animationX, new PropertyPath(TranslateTransform.XProperty));
            Storyboard.SetTargetProperty(animationY, new PropertyPath(TranslateTransform.YProperty));
            Storyboard.SetTargetProperty(animationAngle, new PropertyPath(RotateTransform.AngleProperty));

            story.Begin(this);

效果图如下:

关于路径动画,先介绍到这里,不过其实实现上面的的动画更为常用的方式是使用MatrixAnimationUsingPath,可以参考以下代码:

            Canvas.SetLeft(this.border1, -this.border1.ActualWidth / 2);
            Canvas.SetTop(this.border1, -this.border1.ActualHeight / 2);
            this.border1.RenderTransformOrigin = new Point(0.5, 0.5);

            MatrixTransform matrix = new MatrixTransform();
            this.border1.RenderTransform = matrix;

            NameScope.SetNameScope(this, new NameScope());
            this.RegisterName("matrix", matrix);

            MatrixAnimationUsingPath matrixAnimation = new MatrixAnimationUsingPath();
            matrixAnimation.PathGeometry = this.path1.Data.GetFlattenedPathGeometry();
            matrixAnimation.Duration = new Duration(TimeSpan.FromSeconds(2));
            matrixAnimation.RepeatBehavior = RepeatBehavior.Forever;
            matrixAnimation.AutoReverse = true;
            matrixAnimation.IsOffsetCumulative = !matrixAnimation.AutoReverse;
            matrixAnimation.DoesRotateWithTangent = true;//旋转


            Storyboard story = new Storyboard();
            story.Children.Add(matrixAnimation);
            Storyboard.SetTargetName(matrixAnimation, "matrix");
            Storyboard.SetTargetProperty(matrixAnimation, new PropertyPath(MatrixTransform.MatrixProperty));
            story.Begin(this);

下载Demo:http://files.cnblogs.com/zhouyinhui/PathAnimationDemo.rar

WPF中的PathAnimation(路径动画)的更多相关文章

  1. PowerPoint 2013中创建自定义路径动画的方法

    1.在幻灯片中选择对象,在“动画”选项卡的“高级动画”组中单击“添加动画”按钮,在打开的下拉列表中选择“自定义路径”选项,如图1所示. 图1 选择“自定义路径”选项 2.此时鼠标指针变为十字形,在幻灯 ...

  2. [原][osgEarth]在osgearth中添加相机路径动画

    在osg中添加相机动画路径请参考:http://www.cnblogs.com/lyggqm/p/8075277.html 这里的代码是在osgearth中添加相机动画路径漫游器: #include ...

  3. WPF中的简单水动画

    原文 https://stuff.seans.com/2008/08/21/simple-water-animation-in-wpf/ 很多年前(80年代中期),我在一家拥有Silicon Grap ...

  4. 在 WPF 中使用 Path 路径

    在 WPF 中总会修改 Button 的 Style,比如一个自定义的 Close 按钮.刚入门的可能会用一张 PNG 格式的图片来做这个按钮的 Icon,但这个是不优雅的.而且你要改的时候还得去操作 ...

  5. wpf 中 Ellipse 对象对动画性能的影响

    vs2019 .NetFramework 4.8 win10-64 1909 接手一个wpf项目,某窗口中包含大量的 Shape 对象(线,矩形,圆形等). 这些内容要匀速的向左平移,类似于游戏&qu ...

  6. 示例:WPF中自定义StoryBoarService在代码中封装StoryBoard、Animation用于简化动画编写

    原文:示例:WPF中自定义StoryBoarService在代码中封装StoryBoard.Animation用于简化动画编写 一.目的:通过对StoryBoard和Animation的封装来简化动画 ...

  7. 《深入浅出WPF》笔记——绘画与动画

    <深入浅出WPF>笔记——绘画与动画   本篇将记录一下如何在WPF中绘画和设计动画,这方面一直都不是VS的强项,然而它有一套利器Blend:这方面也不是我的优势,幸好我有博客园,能记录一 ...

  8. WPF中图形表示语法详解(Path之Data属性语法)ZZ

    大可山 [MSN:a3news(AT)hotmail.com] http://www.zpxp.com 萝卜鼠在线图形图像处理 ------------------------------------ ...

  9. WPF中图形表示语法详解(Path之Data属性语法)

    原文 http://blog.csdn.net/johnsuna/article/details/1885597 老规矩,看图说话. 先看显示效果:(图1) XAML(代码A):<Page xm ...

随机推荐

  1. 微信开发学习日记(五):weiphp开源框架的bug,公众号权限编辑问题

    最近在研究weiphp,总体感觉还行,bug据说还挺多. 这不,我就遇到一个比较严重影响使用的.感觉不太应该出现这么严重的bug啊. weiphp的微信公众号等级,权限增加和编辑bug,看不到权限列表 ...

  2. [Vue] Use Vue.js Component Computed Properties

    You can add computed properties to a component to calculate a property on the fly. The benefit of th ...

  3. 从0到1打造直播 App(直播流程介绍整理 <mark><转>)

    注明:原创 2016-10-27 李智文 腾讯Bugly 概要 分享内容: 互联网内容载体变迁历程,文字——图片/声音——视频——VR/AR——……..从直播1.0秀场时代(YY),2.0游戏直播(斗 ...

  4. JFinal redis cluster集群插件

    JFinal redis cluster集群插件 JFinal 框架到了2.1版本号,可是依旧仅仅支持redis的主从集群,没有看到Cluster集群的插件.笔者照着主从的插件方式,改了改,实现了个简 ...

  5. 常见排序算法(java实现)

    常见排序算法介绍 冒泡排序 代码: public class BubbleSort { public static void sort(int[] array) { int tValue; for ( ...

  6. 前端后端分离,怎么解决SEO优化的问题呢?

    对于90%以上的互联网公司来说,前后端分离是必须要做的.目前接手的公司的一个工程,后端是PHP,用的smarty模板,开发效率和之前公司的完全分离相比,确实低不少,一方面需要前端会PHP,另一方面沟通 ...

  7. webpack打包不引入vue、echarts等公共库

    如果我们打包的时候不想将vue.echarts等公共库包含在内,需要配置两处地方, 以下以基于vue-cli生成的项目为基准: 1webpack配置: // webpack.base.conf.js ...

  8. git 本地分支关联远程分支

    github上已经有master分支 和dev分支 在本地 git checkout -b dev 新建并切换到本地dev分支 git pull origin dev 本地分支与远程分支相关联 在本地 ...

  9. C#后台调用前台js方法

    前台js方法(写在head标签之内) <script type="text/javascript" language="javascript"> f ...

  10. 关闭Wind XP/Vista/Win7的DEP数据执行保护汇总(转)

    数据执行保护 (DEP) 是一种Windows安全机制,从Windows版本顺序上看是从Windows XP SP2开始引入,通过监视程序以确保它们使用的系统内存是安全的,帮助防止操作系统受到病毒和其 ...