【WPF学习】第五十六章 基于帧的动画
除基于属性的动画系统外,wpf提供了一种创建基于帧的动画的方法,这种方法只使用代码。需要做的全部工作是响应静态的compositiontarge.rendering事件,触发该事件是为了给每帧获取内容。这是一种非常低级的方法,除非使用标准的基于属性的动画模型不能满足需要(例如,构建简单的侧边滚动游戏、创建基于物理的动画式构建粒子效果模型(如火焰、雪花以及气泡)),否则不会希望使用这种方法。
构建基于帧的动画的基本技术很容易。只需要为静态的compositiontarget.rendering事件关联事件处理程序。一旦关联事件处理程序,wpf就开始不断地调用这个事件处理程序(只要渲染代码的执行速度足够快,wpf每秒将调用60次)。在渲染事件处理程序中,需要在窗口中相应地创建或调整元素。换句话说,需要自行管理全部工作。当动画结束时,分离事件处理程序。
下图显示了一个简单示例。在此,随机数量的圆从canvas面板的顶部向底部下落。它们(根据随机生成的开始速度)以不同速度下降,但一相同的速率加速。当所有的圆到达底部时,动画结束。
在这个示例中,每个下落的圆由ellipse元素表示。使用自定义的ellipseinfo类保存椭圆的引用,并跟踪对于物理模型而言十分重要的一些细节。在这个示例中,只有如下信息很重要——椭圆沿x轴的移动速度(可很容易地扩张这个类,使其包含沿着y轴运动的速度、额外的加速信息等)。
public class ellipseinfo { public ellipse ellipse { get; set; } public double velocityy { get; set; } public ellipseinfo(ellipse ellipse, double velocityy) { velocityy = velocityy; ellipse = ellipse; } }
应用程序使用集合跟踪每个椭圆的ellipseinfo对象。还有几个窗口级别的字段,它们记录计算椭圆下落时使用的各种细节。可很容易地使这些细节变成可配置的。
private list<ellipseinfo> ellipses = new list<ellipseinfo>(); private double accelerationy = 0.1; private int minstartingspeed = 1; private int maxstartingspeed = 50; private double speedratio = 0.1; private int minellipses = 20; private int maxellipses = 100; private int ellipseradius = 10;
当单击其中某个按钮时,清空集合,并将事件处理程序关联到compositiontarget.rendering事件:
private bool rendering = false; private void cmdstart_clicked(object sender, routedeventargs e) { if (!rendering) { ellipses.clear(); canvas.children.clear(); compositiontarget.rendering += renderframe; rendering = true; } } private void cmdstop_clicked(object sender, routedeventargs e) { stoprendering(); } private void stoprendering() { compositiontarget.rendering -= renderframe; rendering = false; }
如果椭圆不存在,渲染代码会自动创建它们。渲染代码创建随机数量的椭圆(当前为20到100个),并使他们具有相同的尺寸和颜色。椭圆被放在canvas面板的顶部,但他们沿着x轴随机移动:
private void renderframe(object sender, eventargs e) { if (ellipses.count == 0) { // animation just started. create the ellipses. int halfcanvaswidth = (int)canvas.actualwidth / 2; random rand = new random(); int ellipsecount = rand.next(minellipses, maxellipses + 1); for (int i = 0; i < ellipsecount; i++) { ellipse ellipse = new ellipse(); ellipse.fill = brushes.limegreen; ellipse.width = ellipseradius; ellipse.height = ellipseradius; canvas.setleft(ellipse, halfcanvaswidth + rand.next(-halfcanvaswidth, halfcanvaswidth)); canvas.settop(ellipse, 0); canvas.children.add(ellipse); ellipseinfo info = new ellipseinfo(ellipse, speedratio * rand.next(minstartingspeed, maxstartingspeed)); ellipses.add(info); } } }
如果椭圆已经存在,代码处理更有趣的工作,以便进行动态显示。使用canvas.settop()方法缓慢移动每个椭圆。移动距离取决于指定的速度。
else { for (int i = ellipses.count - 1; i >= 0; i--) { ellipseinfo info = ellipses[i]; double top = canvas.gettop(info.ellipse); canvas.settop(info.ellipse, top + 1 * info.velocityy); }
为提高性能,一旦椭圆到达canvas面板的底部,就从跟踪集合中删除椭圆。这样,就不需要再处理它们。当遍历集合时,为了能够工作而不会导致丢失位置,需要向后迭代,从集合的末尾向起始位置迭代。
如果椭圆尚未到达canvas面板的底部,代码会提高速度(此外,为获得磁铁吸引效果,还可以根据椭圆与canvas面板底部的距离来设置速度):
if (top >= (canvas.actualheight - ellipseradius * 2 - 10)) { // this circle has reached the bottom. // stop animating it. ellipses.remove(info); } else { // increase the velocity. info.velocityy += accelerationy; }
最后,如果所有椭圆都已从集合中删除,就移除事件处理程序,然后结束动画:
if (ellipses.count == 0) { // end the animation. // there's no reason to keep calling this method // if it has no work to do. stoprendering(); }
示例完整xaml标记如下所示:
<window x:class="animation.framebasedanimation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" title="framebasedanimation" height="396" width="463.2"> <grid margin="3"> <grid.rowdefinitions> <rowdefinition height="auto"></rowdefinition> <rowdefinition></rowdefinition> </grid.rowdefinitions> <stackpanel orientation="horizontal"> <button margin="3" padding="3" click="cmdstart_clicked">start</button> <button margin="3" padding="3" click="cmdstop_clicked">stop</button> </stackpanel> <canvas name="canvas" grid.row="1" margin="3"></canvas> </grid> </window>
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; using system.windows; using system.windows.controls; using system.windows.data; using system.windows.documents; using system.windows.input; using system.windows.media; using system.windows.media.imaging; using system.windows.shapes; namespace animation { /// <summary> /// framebasedanimation.xaml 的交互逻辑 /// </summary> public partial class framebasedanimation : window { public framebasedanimation() { initializecomponent(); } private bool rendering = false; private void cmdstart_clicked(object sender, routedeventargs e) { if (!rendering) { ellipses.clear(); canvas.children.clear(); compositiontarget.rendering += renderframe; rendering = true; } } private void cmdstop_clicked(object sender, routedeventargs e) { stoprendering(); } private void stoprendering() { compositiontarget.rendering -= renderframe; rendering = false; } private list<ellipseinfo> ellipses = new list<ellipseinfo>(); private double accelerationy = 0.1; private int minstartingspeed = 1; private int maxstartingspeed = 50; private double speedratio = 0.1; private int minellipses = 20; private int maxellipses = 100; private int ellipseradius = 10; private void renderframe(object sender, eventargs e) { if (ellipses.count == 0) { // animation just started. create the ellipses. int halfcanvaswidth = (int)canvas.actualwidth / 2; random rand = new random(); int ellipsecount = rand.next(minellipses, maxellipses + 1); for (int i = 0; i < ellipsecount; i++) { ellipse ellipse = new ellipse(); ellipse.fill = brushes.limegreen; ellipse.width = ellipseradius; ellipse.height = ellipseradius; canvas.setleft(ellipse, halfcanvaswidth + rand.next(-halfcanvaswidth, halfcanvaswidth)); canvas.settop(ellipse, 0); canvas.children.add(ellipse); ellipseinfo info = new ellipseinfo(ellipse, speedratio * rand.next(minstartingspeed, maxstartingspeed)); ellipses.add(info); } } else { for (int i = ellipses.count - 1; i >= 0; i--) { ellipseinfo info = ellipses[i]; double top = canvas.gettop(info.ellipse); canvas.settop(info.ellipse, top + 1 * info.velocityy); if (top >= (canvas.actualheight - ellipseradius * 2 - 10)) { // this circle has reached the bottom. // stop animating it. ellipses.remove(info); } else { // increase the velocity. info.velocityy += accelerationy; } if (ellipses.count == 0) { // end the animation. // there's no reason to keep calling this method // if it has no work to do. stoprendering(); } } } } } public class ellipseinfo { public ellipse ellipse { get; set; } public double velocityy { get; set; } public ellipseinfo(ellipse ellipse, double velocityy) { velocityy = velocityy; ellipse = ellipse; } } }
显然,可扩展的这个动画以使圆跳跃和分散等。使用的技术是相同的——只需要使用更复杂的公式计算速度。
当构建基于帧的动画时需要注意如下问题:它们不依赖与时间。换句话说,动画可能在性能好的计算机上运动更快,因为帧率会增加,会更频繁地调用compositiontarget.rendering事件。为补偿这种效果,需要编写考虑当前时间的代码。
开始学习基于帧的动画的最好方式是查看wpf sdk提供的每一帧动画都非常详细的示例。该例演示了几种粒子系统效果,并且使用自定义的timetracker类实现了依赖与时间的基于帧的动画。
上一篇: MongoDB Shell 命令实例总结【进阶篇】
下一篇: MySQL如何解决DOS窗口乱码问题