原文:WPF随笔(九)--使用路径动画模拟管道流体流向

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lordwish/article/details/85007867

WPF的一大特性就的动画系统,使用动画能够实现很多在WinForm很难实现的效果。最近在网上偶然看到大神用WPF动画实现对象沿特定路径正向或反向移动的效果,就想参考着自己试一试。


1.简单路径动画

先来一个最简单的路径动画,一个方块加一条线段,让方块从线段起点移动到线段终点。前台页面代码如下:

    <Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<WrapPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Button x:Name="btnAnimo" Click="btnAnimo_Click" Margin="0,0,10,0">开始</Button>
</WrapPanel>
<Grid Grid.Row="1">
<Canvas x:Name="cvsMain">
<Path x:Name="path1" Data="M100,100 L300,100 400,200 500,200" Stroke="LightGreen" StrokeThickness="20" StrokeLineJoin="Round"></Path>
</Canvas>
</Grid>
</Grid>

后台逻辑代码如下:

        private void btnAnimo_Click(object sender, RoutedEventArgs e)
{
AnimationByPath(cvsMain, path1,path1.StrokeThickness);
} /// <summary>
/// 路径动画
/// </summary>
/// <param name="cvs">画板</param>
/// <param name="path">路径</param>
/// <param name="target">动画对象</param>
/// <param name="duration">时间</param>
private void AnimationByPath(Canvas cvs, Path path,double targetWidth, int duration = 5)
{
#region 创建动画对象
Rectangle target = new Rectangle();
target.Width = targetWidth;
target.Height = targetWidth;
target.Fill = new SolidColorBrush(Colors.Orange);
cvs.Children.Add(target);
Canvas.SetLeft(target, -targetWidth / 2);
Canvas.SetTop(target, -targetWidth / 2);
target.RenderTransformOrigin = new Point(0.5, 0.5);
#endregion MatrixTransform matrix = new MatrixTransform();
TransformGroup groups = new TransformGroup();
groups.Children.Add(matrix);
target.RenderTransform = groups;
string registname = "matrix" + Guid.NewGuid().ToString().Replace("-", "");
this.RegisterName(registname, matrix);
MatrixAnimationUsingPath matrixAnimation = new MatrixAnimationUsingPath();
matrixAnimation.PathGeometry = PathGeometry.CreateFromGeometry(Geometry.Parse(path.Data.ToString()));
matrixAnimation.Duration = new Duration(TimeSpan.FromSeconds(duration));
matrixAnimation.DoesRotateWithTangent = true;//跟随路径旋转
matrixAnimation.RepeatBehavior = RepeatBehavior.Forever;//循环
Storyboard story = new Storyboard();
story.Children.Add(matrixAnimation);
Storyboard.SetTargetName(matrixAnimation, registname);
Storyboard.SetTargetProperty(matrixAnimation, new PropertyPath(MatrixTransform.MatrixProperty)); story.FillBehavior = FillBehavior.Stop;
story.Begin(target, true);
}

其中的关键点在于动态创建一个Rectangle正方体作为动画对象,正方体的宽高设为跟路径宽度一致,并设置正方体的变换原点为中心点(RenderTransformOrigin =“0.5,0.5”),确保正方体随路径移动时也能随着路径旋转。最终效果如下:

2.反向路径动画

在上个示例的基础上,将线段改成多条连续线段甚至加上弧线都不影响效果,小方块都会沿着路径移动下去。对于一条路径来讲是有起点和终点的,正常情况下动画对象是从起点移动到终点的,能否让对象从终点移动到起点呢?

其是换个思路思考,将原有路径反转,起点、终点对调,不就能得到一条与原路径外观一致但数据相反的路径了吗?让动画对象沿着反转后的路径移动,从视觉效果上来看就是从终点移动到起点了。

解决这个问题的关键就在于路径数据的转换了。

        private string ConvertPathData(string data)
{
data = data.Replace("M", "");
Regex regex = new Regex("[a-z]", RegexOptions.IgnoreCase);
MatchCollection mc = regex.Matches(data);
//item1 从上一个位置到当前位置开始的字符 (match.Index=原始字符串中发现捕获的子字符串的第一个字符的位置。)
//item2 当前发现的匹配符号(L C Z M)
List<Tuple<string, string>> tmps = new List<Tuple<string, string>>();
int index = 0;
for (int i = 0; i < mc.Count; i++)
{
Match match = mc[i];
if (match.Index != index)
{
string str = data.Substring(index, match.Index - index);
tmps.Add(new Tuple<string, string>(str, match.Value));
}
index = match.Index + match.Length;
if (i + 1 == mc.Count)//last
{
tmps.Add(new Tuple<string, string>(data.Substring(index), match.Value));
}
}
List<string[]> arrys = new List<string[]>();
Regex regexnum = new Regex(@"(\-?\d+\.?\d*)", RegexOptions.IgnoreCase);
for (int i = 0; i < tmps.Count; i++)
{
MatchCollection childMcs = regexnum.Matches(tmps[i].Item1);
if (childMcs.Count % 2 != 0)
{
continue;
}
int groups = childMcs.Count / 2;
var strTmp = new string[groups];
for (int j = 0; j < groups; j++)
{
string cdatas = childMcs[j * 2] + "," + childMcs[j * 2 + 1];//重组数据
strTmp[j] = cdatas;
}
arrys.Add(strTmp);
} List<string> result = new List<string>();
for (int i = arrys.Count - 1; i >= 0; i--)
{
string[] clist = arrys[i];
for (int j = clist.Length - 1; j >= 0; j--)
{
if (j == clist.Length - 2 && i > 0)//对于第二个元素增加 L或者C的标识
{
var pointWord = tmps[i - 1].Item2;//获取标识
result.Add(pointWord + clist[j]);
}
else
{
result.Add(clist[j]);
if (clist.Length == 1 && i > 0)//说明只有一个元素 ex L44.679973,69.679973
{
result.Add(tmps[i - 1].Item2);
}
}
}
}
return "M" + string.Join(" ", result); }

另外作为动画对象的正方体可以换成任意控件对象,为了形象点,就把正方体换成箭头;同时为了区分正向和反向动画,路径也设置成不同的颜色。修改之后的代码如下:

        /// <summary>
/// 正向
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAnimo_Click(object sender, RoutedEventArgs e)
{
AnimationByPath(cvsMain, path1,path1.StrokeThickness,false,3);
}
/// <summary>
/// 反向
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnReback_Click(object sender, RoutedEventArgs e)
{
AnimationByPath(cvsMain, path1, path1.StrokeThickness, true, 3);
} /// <summary>
/// 路径动画
/// </summary>
/// <param name="cvs">画板</param>
/// <param name="path">路径</param>
/// <param name="targetWidth">动画对象宽高</param>
/// <param name="isInverse">是否反向</param>
/// <param name="duration">动画时间</param>
private void AnimationByPath(Canvas cvs, Path path, double targetWidth, bool isInverse = false, int duration = 5)
{
Polygon target = new Polygon();
target.Points = new PointCollection()
{
new Point(0,0),
new Point(targetWidth/2,0),
new Point(targetWidth,targetWidth/2),
new Point(targetWidth/2,targetWidth),
new Point(0,targetWidth),
new Point(targetWidth/2,targetWidth/2)
}; if (isInverse)//反向
{
target.Fill = new SolidColorBrush(Colors.DeepSkyBlue);
}
else//正向
{
target.Fill = new SolidColorBrush(Colors.Orange);
} cvs.Children.Add(target);
Canvas.SetLeft(target, -targetWidth / 2);
Canvas.SetTop(target, -targetWidth / 2);
target.RenderTransformOrigin = new Point(0.5, 0.5); MatrixTransform matrix = new MatrixTransform();
TransformGroup groups = new TransformGroup();
groups.Children.Add(matrix);
target.RenderTransform = groups;
string registname = "matrix" + Guid.NewGuid().ToString().Replace("-", "");
this.RegisterName(registname, matrix);
MatrixAnimationUsingPath matrixAnimation = new MatrixAnimationUsingPath();
if (!isInverse)//正向
{
matrixAnimation.PathGeometry = PathGeometry.CreateFromGeometry(Geometry.Parse(path.Data.ToString()));
}
else//反向
{
string data = ConvertPathData(path.Data.ToString());
matrixAnimation.PathGeometry = PathGeometry.CreateFromGeometry(Geometry.Parse(data));
}
matrixAnimation.Duration = new Duration(TimeSpan.FromSeconds(duration));
matrixAnimation.DoesRotateWithTangent = true;//旋转
matrixAnimation.RepeatBehavior = RepeatBehavior.Forever;
Storyboard story = new Storyboard();
story.Children.Add(matrixAnimation);
Storyboard.SetTargetName(matrixAnimation, registname);
Storyboard.SetTargetProperty(matrixAnimation, new PropertyPath(MatrixTransform.MatrixProperty)); story.FillBehavior = FillBehavior.Stop;
story.Begin(target, true);
}

效果就变成下面的样子了,是不是有点意思了。

3.模拟管道流体动画

有了上面的基础,就考虑改进一下做个模拟水管中水流动的动画效果。管子当然不止一根,要多根,管径也不同;再加个水泵,水泵启动水就流动,水泵反转,水就倒流。因为在上一步已经解决了最核心的问题,这步加个关键帧动画用来控制动画对象旋转就好了。

前台代码改为:

    <Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<WrapPanel VerticalAlignment="Center" HorizontalAlignment="Center">
<Button x:Name="btnAnimo" Click="btnAnimo_Click" Margin="0,0,10,0">正转</Button>
<Button x:Name="btnReback" Click="btnReback_Click" Margin="0,0,10,0">反转</Button>
</WrapPanel>
<Grid Grid.Row="1">
<Canvas x:Name="cvsMain">
<Path x:Name="path1" Data="M100,100 L300,100 300,200 400,200" Stroke="LightGreen" StrokeThickness="20" StrokeLineJoin="Round"></Path>
<Path x:Name="path2" Data="M200,300 L350,300 350,200" Stroke="LightGreen" StrokeThickness="12" StrokeLineJoin="Round"></Path>
<Path x:Name="path3" Data="M450,223 L550,223 650,100 750,100 800,150" Stroke="LightGreen" StrokeThickness="16" StrokeLineJoin="Round"></Path>
<Image Source="fan.png" Width="50" Height="50" Canvas.Left="400" Canvas.Top="185"></Image>
<Image x:Name="imgFan" Source="fan-inner.png" Width="24" Height="24" Canvas.Left="410" Canvas.Top="197" RenderTransformOrigin="0.5,0.5"></Image>
</Canvas>
</Grid>
</Grid>

后台代码修改为:

        /// <summary>
/// 正转
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAnimo_Click(object sender, RoutedEventArgs e)
{
AnimationByPath(this.cvsMain, this.path1, this.path1.Width,false, 3);
AnimationByPath(this.cvsMain, this.path2, this.path2.Width,false, 3);
AnimationByPath(this.cvsMain, this.path3, this.path3.Width,false, 3); StoryByOrient(this.imgFan,0, 3);
}
/// <summary>
/// 反转
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnReback_Click(object sender, RoutedEventArgs e)
{
AnimationByPath(this.cvsMain, this.path1, this.path1.Width, true, 3);
AnimationByPath(this.cvsMain, this.path2, this.path2.Width, true, 3);
AnimationByPath(this.cvsMain, this.path3, this.path3.Width, true, 3); StoryByOrient(this.imgFan, 1, 3);
} /// <summary>
/// 旋转动画
/// </summary>
/// <param name="img">动画对象</param>
/// <param name="orientation">顺时针/逆时针</param>
/// <param name="duration"></param>
private void StoryByOrient(Image img, int orientation, int duration = 5)
{
Storyboard storyboard = new Storyboard();//创建故事板
DoubleAnimation doubleAnimation = new DoubleAnimation();//实例化一个Double类型的动画
RotateTransform rotate = new RotateTransform();//旋转转换实例
img.RenderTransform = rotate;//给图片空间一个转换的实例
storyboard.RepeatBehavior = RepeatBehavior.Forever;//设置重复为 一直重复
storyboard.SpeedRatio = 2;//播放的数度
//设置从0 旋转360度
doubleAnimation.From = 0;
if (orientation==0)//顺时针
{
doubleAnimation.To = 360;
}
else//逆时针
{
doubleAnimation.To = -360;
}
doubleAnimation.Duration = new Duration(TimeSpan.FromSeconds(duration));//播放时间长度为2秒
Storyboard.SetTarget(doubleAnimation, img);//给动画指定对象
Storyboard.SetTargetProperty(doubleAnimation,
new PropertyPath("RenderTransform.Angle"));//给动画指定依赖的属性
storyboard.Children.Add(doubleAnimation);//将动画添加到动画板中
storyboard.Begin(img);//启动动画
}

来看看最终的效果:

还挺像那么回事的


编程很有趣,一刻不放弃

WPF随笔(九)--使用路径动画模拟管道流体流向的更多相关文章

  1. WPF中的PathAnimation(路径动画)

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

  2. WPF 精修篇 路径动画

    原文:WPF 精修篇 路径动画 路径动画 是让一个对象围绕指定Path 的运动路径 进行移动的动画 举栗子 路径动画 使用 Blend 来设置 是十分简单的 首先用工具 笔  点出一条线 新建一个圆形 ...

  3. WPF动画之路径动画(3)

    XAML代码: <Window x:Class="路径动画.MainWindow" xmlns="http://schemas.microsoft.com/winf ...

  4. WPF中的动画——(五)路径动画

    路径动画是一种专门用于将对象按照指定的Path移动的动画,虽然我们也可以通过控制动画的旋转和偏移实现对象的移动,但路径动画更专业,它的实现更加简洁明了. 路径动画中最常用的是MatrixAnimati ...

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

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

  6. WPF学习之绘图和动画

    如今的软件市场,竞争已经进入白热化阶段,功能强.运算快.界面友好.Bug少.价格低都已经成为了必备条件.这还不算完,随着计算机的多媒体功能越来越强,软件的界面是否色彩亮丽.是否能通过动画.3D等效果是 ...

  7. WPF学习之绘图和动画--DarrenF

    Blend作为专门的设计工具让WPF如虎添翼,即能够帮助不了解编程的设计师快速上手,又能够帮助资深开发者快速建立图形或者动画的原型. 1.1   WPF绘图 与传统的.net开发使用GDI+进行绘图不 ...

  8. WPF学习(12)动画

    本篇来学习WPF的动画.什么是动画?动画就是一系列帧.在WPF中,动画就是在一段时间内修改依赖属性值的行为,它是基于时间线Timeline的.有人会说,要动画干嘛,华而不实,而且添加了额外的资源消耗而 ...

  9. 通通WPF随笔(4)——通通手写输入法(基于Tablet pc实现)

    原文:通通WPF随笔(4)--通通手写输入法(基于Tablet pc实现) 从我在博客园写第一篇博客到现在已经有1年半了,我的第一篇博客写的就是手写识别,当时,客户需求在应用中加入手写输入功能,由于第 ...

随机推荐

  1. do_pj--下拉代码脚本的使用

    接本目录 /home/zhangshuli/git2/vanzo_team/xulei/Platform.py 在~/bin目录下链接 ln -sf ~/git2/vanzo_team/xulei/P ...

  2. css中的!important作用

    css中的!important作用 一.总结 1.!important:是hack, 2.!important作用:让浏览器首选执行这个语句,当对同一个对象设置了多个同类型的属性的时候,首选执行这一个 ...

  3. 一些常用JS函数和技巧总结

    1.JS原生函数parseInt(),返回字符串的第一个数字,默认是十进制. 2.!!data.success  //强制转换成布尔类型

  4. vue 星星评分组件

    显示评分和打分组件,可现实半颗星星效果 效果图: 参数名 类型 说明 score Number 分数 ,默认0,保留一位小数 disabled Boolean 是否只读,默认false,鼠标点击可以打 ...

  5. valueof(), intvalue(0 parseint() 这三个方法怎么用

    valueOf(int i) 返回一个表示指定的 int 值的 Integer 实例.valueOf(String s) 返回保存指定的 String 的值的 Integer 对象.valueOf(S ...

  6. 洛谷 P1657 选书

    P1657 选书 题目描述 学校放寒假时,信息学奥赛辅导老师有1,2,3……x本书,要分给参加培训的x个人,每人只能选一本书,但是每人有两本喜欢的书.老师事先让每个人将自己喜欢的书填写在一张表上.然后 ...

  7. 11.5 Android显示系统框架_Vsync机制_代码分析

    5.5 surfaceflinger对vsync的处理buffer状态图画得不错:http://ju.outofmemory.cn/entry/146313 android设备可能连有多个显示器,AP ...

  8. GPUImage ==> 一个基于GPU图像和视频处理的开源iOS框架

    Logo 项目介绍: GPUImage是Brad Larson在github托管的开源项目. GPUImage是一个基于GPU图像和视频处理的开源iOS框架,提供各种各样的图像处理滤镜,并且支持照相机 ...

  9. 如何启用“锁定内存页”选项 (Windows)

    默认情况下,禁用 Windows 策略"锁定内存页"选项.必须启用此权限才能配置地址窗口化扩展插件 (AWE).此策略将确定哪些帐户可以使用进程将数据保留在物理内存中,从而阻止系统 ...

  10. VSX(翻译)Moving Code Blocks Among Code Regions using VS 2010 Extensions

    Moving Code Blocks Among Code Regions using VS 2010 Extensions (翻译)使用VS 2010 扩展性将代码块移至Region区域中 Down ...