【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>

FrameBasedAnimation.xaml

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;        }    }}

FrameBasedAnimation.xaml.cs

  显然,可扩展的这个动画以使圆跳跃和分散等。使用的技术是相同的——只需要使用更复杂的公式计算速度。

  当构建基于帧的动画时需要注意如下问题:它们不依赖与时间。换句话说,动画可能在性能好的计算机上运动更快,因为帧率会增加,会更频繁地调用CompositionTarget.Rendering事件。为补偿这种效果,需要编写考虑当前时间的代码。

  开始学习基于帧的动画的最好方式是查看WPF SDK提供的每一帧动画都非常详细的示例。该例演示了几种粒子系统效果,并且使用自定义的TimeTracker类实现了依赖与时间的基于帧的动画。

(0)

相关推荐

  • WPF实现Map加载

    以下文章来源于WPF开发者 ,作者驚鏵 接着上一篇 效果预览: 一.MainWindow.xaml代码如下: <Window x:Class="WpfBingMap.MainWindo ...

  • WPF 动画实战 点击时显示圆圈淡出效果

    本文告诉大家一个有趣的动画,在鼠标点击的时候,在点击所在的点显示一个圆圈,然后这个圆圈做动画变大,但是颜色变淡的效果.本文的控件可以让大家将对应的容器放在自己应用里面就能实现这个效果 这个效果特别简单 ...

  • 【WPF学习】第十六章 键盘输入

    当用户按下键盘上的一个键时,就会发生一系列事件.下表根据他们的发生顺序列出了这些事件: 表 所有元素的键盘事件(按顺序) 键盘处理永远不会像上面看到的这么简单.一些控件可能会挂起这些事件中的某些事件, ...

  • 你也能看懂《道德经》,第五十六章

    有智慧的人不多说话,多说话的人缺乏智慧. 堵住欲望的通道,关闭凡俗的大门.消磨万物的锐气,解除万物的纷扰,同和万物的光辉,与万物同在尘埃.这是玄妙的协同. 因此不可以得到了就亲近,不可以得到了就疏远: ...

  • 《道德经》第五十六章  和光同尘

    [原文] 知者不言,言者不知.塞其兑(duì),闭其门:挫其锐,解其纷:和其光,同其尘,是谓玄同.故不可得而亲,不可得而疏:不可得而利,不可得而害:不可得而贵,不可得而贱.故为天下贵. [译文] 知& ...

  • 深度解析老子《道德经》第五十六章

    大家好,这里是三宝易学堂,今天为大家带来的是老子<道德经>第五十六章,首先我们来先看原文: 知者不言,言者不知. 塞其兑,闭其门:挫其锐,解其纷:和其光,同其尘,是谓玄同. 故不可得而亲, ...

  • 【宦海悲歌】第五十六章 主持大家庭,头绪繁多,用度繁杂,没有点吃亏精神是办不好的

    我丁忧在家,妻妾奴婢十余人,米盐零杂.生活用度花销是个不小的数目. 有一天兼祧父检点账目,不禁发愁,劝我早想点办法,不能这样坐吃山空. 其实,我一个在家守孝的道员也没有更好的挣钱办法. 无奈,只好发挥 ...

  • 道德经第五十六章——知者不言

    道德经这本书,有大智慧,这是基本达成共识的,但是,这智慧到底有多大,到底有所少可以领悟,看个人,本章,就非常的具有指导意义. 先贴下原文: 知者不言,言者不知.塞其兑,闭其门:挫其锐,解其纷:和其光, ...

  • 阴魂不散●第五十六章 生死之际

    (朗读者:自洽) 欢迎关注佳丽世界,以方便<阴魂不散>小说阅读收听. 最让他怦然心动的是一次考试补习,班主任让结对子.刚好那个女生的英语好,物理弱一些.顾维之是英语弱物理和语文强.于是两个 ...

  • 《女娲图的秘密》第五十六章:奇怪的黑影

    女娲图的秘密 长篇奇幻小说 <女娲图的秘密> 即将出版 免费抢先看 连载中-- 第五十六章 奇怪的黑影 第五十六章 琪儿公主疑惑地问高鼐胤:"定位是什么?" " ...

  • 《广州棋坛六十年》第一百五十六章 华东棋国的新壁垒

    第一百五十六章 华东棋国的新壁垒 从三十年代开始,以上海为中心的华东棋坛代表人物同广州棋手进行了最为频繁的棋艺交流.经抗日战争的影响,两地棋手备受摧折,广州方面,黄松轩.冯敬如.钟珍等相继凋萎,继起的 ...