通常,为用户界面应用动画只不过是创建并配置正确的动画和故事板对象。但在其他情况下,特别是同时发生多个动画时,可能需要更加关注性能。特定的效果更可能导致这些问题——例如,那些涉及视频、大位图以及多层透明等的效果通常需要占用更多CPU开销。如果不谨慎实现这类效果,运行它们使可能造成明显抖动,或者会从其他同时运行的应用程序抢占CPU时间。
幸运的是,WPF提供了几个可提供帮助的技巧。接下来的几节将学习降低最大帧率以及缓存计算机显卡中的位图,这两种技术可以减轻CPU的负担。
一、期望的帧率
正如前面所学习的,WPF试图保持以60帧/秒的速度运动动画。这样可确保从开始到结束得到平滑流畅的动画。当然,WPF可能达不到这个目标。如果同时运行多个复杂的动画,并且CPU或显卡不能承受的话,整个帧率可能会下降(最好的情形),甚至可能会跳跃以进行补偿(最坏的情形)。
尽管很少提高帧率,但可能会选择降低帧率,这可能是因为以下两个原因之一:
调整帧率很容易。只需要为包含动画的故事板使用Timeline.DesiredFrameRate附加属性。下面的示例将帧率减半:
<Storyboard Timeline.DesiredFrameRate="30">
下图显示了一个简单的测试程序,该程序为一个小球应用动画,使其在Canvas控件上沿一条曲线运动。
这个应用程序开始在Canvas上绘制Ellipse对象。Canvas.ClipToBounds属性被设置为true,所以圆的边缘不会超出Canvas控件的边缘而进入窗口的其他部分。
<Canvas ClipToBounds="True"> <Ellipse Name="ellipse" Fill="Red" Width="10" Height="10">Ellipse> Canvas>
为在Canvas控件上移动圆,需要同时进行两个动画——一个动画用于更新Canva.Left属性(从左向右移动圆),另一个动画用于改变Canvas.Top属性(使圆上升,然后下降)。Canvas.Top动画是可反转的——一旦圆达到最高点,就会下降。Canvas.Left动画不是可反转的,但持续时间是Canvas.Top动画的两倍,从而使得这两个动画可以同时移动圆。最后的技巧是为Canvas.Top动画使用DeceleartionRatio属性。这样,当圆达到最高点是上升的速度会更慢,这会创建更逼真的效果。
下面是动画的完整标记:
<Window.Resources> <BeginStoryboard x:Key="beginStoryboard"> <Storyboard Timeline.DesiredFrameRate="{Binding ElementName=txtFrameRate,Path=Text}"> <DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(Canvas.Left)" From="0" To="300" Duration="0:0:5"> DoubleAnimation> <DoubleAnimation Storyboard.TargetName="ellipse" Storyboard.TargetProperty="(Canvas.Top)" From="300" To="0" AutoReverse="True" Duration="0:0:2.5" DecelerationRatio="1"> DoubleAnimation> Storyboard> BeginStoryboard> Window.Resources>
这个示例的真正目的是尝试不同的帧率。为查看某个特定帧率的效果,只需要在文本框中输入合适的数值,然后单击Repeat按钮即可。然后动画就会使用新的帧率(通过数据绑定表达式获取新的帧率)触发,从而可以观察动画的效果。在更低的帧率下,椭圆不会均匀移动——而会在Cavans控件中的跳跃。
也可使用代码调整Timeline.DesiredFrame属性。例如,可能希望读取静态属性RenderCapability.Tier以确定显卡支持的渲染级别。
二、位图缓存
位图缓存通知WPF获取内容的当前位图图像,并将其复制到显卡的内存中。这时,显卡可以控制位图的操作和显示的刷新。这个处理过程比让WPF完成所有工作要快很多,并且和显卡不断通信。
如果运用得当,位图缓存可以改善应用程序的绘图性能。但如果运用不当,就会浪费显存并且实际上会降低性能。所以,在使用位图缓存之前,需要确保真正合适。下面列出一些指导原则:
为更好地理解位图缓存,使用一个简单示例是有帮助的,下图例举一个示例,一个动画推动一个简单的图像——正方形——在Canvas面板上移动,Canvas面板包含一条具有复杂集合图形的路径。但正方形在Canvas面板表面上移动时,强制WPF重新计算路径并填充丢失的部分。这会带来极大的CPU负担,并且动画甚至可能开始变得断断续续。
可采用几种方法解决该问题。一种选择是使用一幅位图替换背景,WPF能够更高效地管理位图。更灵活的选择是使用位图缓存,这种方法可继续将存活的、可交互的元素作为背景。
为启用位图缓存功能,将相应元素的CacheMode属性设置为BitmapCache。每个元素都提供了CacheMode属性,这意味着可以精确选择为哪个元素使用这一特征。
<Path CacheMode="BitmapCache" ...>Path>
通过这个简单修改,可立即看到区别。首先,窗口显示的事件要稍长一些。但动画的运行将更平滑,并且CPU的负担将显著降低。可通过Windows任务管理器进行检查——经常可以看到CPU的负担从接近100%减少到20%一下。
通常,当启用位图缓存时,WPF采用元素当前尺寸的快照并将其位图复制到显卡中。如果之后使用ScaleTransform放大元素,这会变成一个问题。在这种情况下,将放大缓存的位图,而不是实际的元素,当放大元素时这会导致模糊放大以及色块。
例如,设想一个修订过的示例。在这个示例中,第二个同步动画扩展Path使其为原始尺寸的10倍,然后缩回原始尺寸。为确保具有良好的显示质量,可使用5倍于Path原始尺寸的尺寸缓存其位图:
<Path ...> <Path.CacheMode> <BitmapCache RenderAtScale="5">BitmapCache> Path.CacheMode> Path>
这样可解决像素化问题。虽然缓存的位图仍比Path的最大动画尺寸(最大尺寸达10倍于其原始尺寸)小,但显卡能使位图的尺寸加倍,从5倍到10倍,而不会有任何明显的缩放问题。更重要的是,这可使应用避免过多地使用显存。