8.1 动画方案的选择

    Windows Phone的动画实现方式有线性插值动画(3种类型)、关键祯动画(4种类型)和基于帧动画,甚至还有定时器动画,然后动画所改变的UI元素属性可以是普通的UI元素属性,变换特效属性和三维特效属性,面对着这么多的选择,我们要实现一个动画效果该怎么去思考动画实现的思路以及怎么选择实现的技术呢?那么我们这小节会先讲解与动画性能相关的知识,然后再讲解怎么去选择动画的实现方案。

8.1.1 帧速率

    帧速率是用于测量显示帧数的量度,测量单位为“每秒显示帧数”(Frame per Second,FPS,帧率)或“赫兹”,是指每秒钟刷新的画面的帧数,也可以理解为图形处理器每秒钟能够刷新几次。由于人类眼睛的特殊生理结构,如果所看画面之帧率高于每秒约10-12帧的时候,就会认为是连贯的。对于动画而言,帧速率常用于衡量动画的流畅度,帧速率的数字越大表示动画的流畅度越高。在实现Windows Phone动画的时候,我们是不能够直接指定动画的帧速率的,动画的帧速率是由系统自动分配的,当手机的性能越好,程序的性能越好,那么动画的帧速率就越大,反之就越小。所以要判断一个动画是否能够流畅地运行,我们需要关注动画的帧速率指标是否足够高。

    在Windows Phone里面虽然不能够直接设置动画的帧速率,但是可以测量出来。当在Windows Phone模拟器中运行应用时,可以使用帧速率计数器来监控应用的性能和动画的效率,模拟器的效果图如图8.1所示,每一个帧速率计数器的作用如表8.1所示。当然帧速率计数器也一样可以在手机上进行显示,在真实的 Windows Phone 手机上测试这些计数器非常重要,因为模拟器的性能和在真实的手机上是有很大区别的。对于每个计数器的值都有建议阈值和上限阈值,如表8.2所示,当计数器在红色值阈值区间表明存在潜在性能问题,这就需要引起重视,你的动画的实现方案可能有较大的问题,需要进行优化。

 那么帧速率计数器是可以在代码中启用或禁用的,当你在Visual Studio中创建 Windows Phone应用项目时,默认情况下会在文件 App.xaml.cs 中添加启用帧速率计数器的代码。代码如下所示:

    if (System.Diagnostics.Debugger.IsAttached)

    {

        this.DebugSettings.EnableFrameRateCounter = true;

    }

    上面的代码表示当启动Debug状态调试应用程序的时候将会启用帧速率计数器。其中Application.Current.Host.Settings.EnableFrameRateCounter = true表示启用帧速率计数器,设置为false则禁用帧速率计数器。

8.1.2 UI线程和构图线程

    Windows Phone的图形线程结构针对手机进行了优化,除了UI线程之外,Windows Phone 还支持构图线程。若要掌握怎么去选择最优的动画实现方案,那么需要理解Windows Phone 中UI线程和构图线程,这对做动画的优化是非常重要的。

    (1)UI 线程

    UI 线程是Windows Phone中的主线程,UI线程的主要任务是从 XAML 中分析并创建对象、在第一次绘制视觉效果时,将绘制所有视觉效果以及处理每帧回调并执行其他用户代码。在应用程序里面维护轻量级的UI线程是保障应用程序流畅运行的前提,同时这对于动画的实现也是一样的道理,尽量避免占用UI线程。

    (2)构图线程

    构图线程可以处理某些在UI上的工作,从而分担了UI线程的部分工作,提高Windows Phone应用的性能。在Windows Phone上,构图线程的工作是,它合并图形纹理并将其传递到 GPU 以供绘制,手机上的 GPU 将在称为自动缓存的进程中,自动缓存并处理运行在构图线程上的动画。构图线程处理与变换特效(RenderTransform)和三维特效(Projection)属性关联的动画,如针对于ScaleTransform、TranslateTransform、RotateTransform和PlaneProjection的属性改变的Storyboard动画都是完全运行在构图线程上的。另外,Opacity 和 Clip 属性设置也由构图线程处理。但是,如果使用 OpacityMask或非矩形剪辑,则这些操作将被传递到 UI 线程。

    (3)动画和线程

    从构图线程的作用可以知道StoryBoard动画由构图线程进行处理,那么这种动画的处理方式最为理想,因为构图线程会将这些动画传递到GPU进行处理。如果需要在动画中使用到UI线程,如改变UI元素的With属性等,那么就需要给动画相应的Animation对象的EnableDependentAnimation属性设置为True,它表示动画是否需要依赖UI线程来运行。此外,如果 CPU 超负荷,则构图线程可能比UI线程运行的更频繁。但是,有时Storyboard动画无法实现你的动画效果的时候,你可以选择在代码中驱动动画,如采用基于帧动画。这些动画按帧进行处理,每帧回调都在UI线程上进行处理,动画的更新速度与UI线程处理动画的速度相当,并且根据应用中发生的其他操作,动画显示的流畅性可能低于在构图线程上运行的动画。另外,当使用基于帧动画在代码中更新动画时,UI元素不会像在Storyboard动画中更新一样,自动进行缓存,这又加重了UI线程的负担。

8.1.3 选择最优的动画方案

    上一章我们讲解了很多的动画的变成知识,这些都是Windows Phone动画编程的根基,正所谓万变不离其宗,无论你要实现的动画懂么复杂,都离不开这些基础知识。当我们要去实现一个动画效果的时候,首先需要去思考动画中的每个组成元素,思考它们的变化情况,想一下要改变UI元素的什么属性来实现动画的效果,想一下用什么动画类型来实现。当你已经想到了有多种方案可以实现这个动画效果的时候,你可以从两个方面去衡量你的实现方案,一方面是从性能效率方面,这就涉及到前面所讲的动画的帧速率,UI线程和构图线程相关的知识;另一方面是从动画实现的复杂度方面,比如要实现一个很复杂图形的形状变化的动画,你可以直接用Path图形来绘制出这个图形,然后设计Path图形的点运动的动画,也可以用多张类似的图片做图片切换的动画,如果图片切换的动画效果能达到你所想要的效果,那么就建议使用图片切换这种简单的方式来实现。

    在Windows Phone中有多种实现动画的方案,关于这些方案的选择有下面的一些建议。

    (1)可以用变换特效属性或者三维特效属性实现的动画,应该尽量采用变换特效属性或者三维特效属性作为动画改变的属性去实现动画。因为变换特效属性或者三维特效属性是通过构图线程对UI元素产生作用的,不会阻塞UI线程也不会重新调用UI的布局系统。

    (2)可以使用线性插值动画/关键帧动画来实现的动画就采用线性插值动画/关键帧动画去实现,因为线性插值动画/关键帧动画是最优的动画实现方式,它们本身也是在构图线程上运行的。

    (3)当使用线性插值动画/关键帧动画无法实现的动画效果的时候应该采用基于帧动画来实现,而不是自定义定时器来实现动画,基于帧动画比定时器动画更胜一筹,它可以根据设备和应用程序的情况动态地跳帧调用的频率。

    下面我们通过一个例子来演示用两种不同的方法来实现一个相同的动画效果,所实现的动画效果是让矩形的高度慢慢地变成原来的两倍,第一种方式是用线性插值动画对矩形的Height属性进行动画处理,第二种方式也是用线性插值动画,但是针对的动画目标属性是ScaleTransform的ScaleY属性,然后我们用一个按钮点击事件阻塞UI线程2秒钟,可以看到针对Height属性的动画会暂停2秒钟再继续运行,而针对ScaleTransform的ScaleY属性不会受UI线程阻塞的影响。示例代码如下所示:

代码清单8-1两种动画的对UI线程的影响(源代码:第8章\Examples_8_1)

MainPage.xaml文件主要代码
------------------------------------------------------------------------------------------------------------------    < Page.Resources>
        <Storyboard x:Name="heightStoryboard">
            <!--针对Height属性的动画-->
            <DoubleAnimation Storyboard.TargetName="rectangle1" Storyboard.TargetProperty="Height" RepeatBehavior="Forever" EnableDependentAnimation="True" From="100" To="200" Duration="0:0:2">        
            </DoubleAnimation>
        </Storyboard>
        <Storyboard x:Name="scaleTransformStoryboard">
            <!--针对ScaleTransform的ScaleY属性的动画-->
            <DoubleAnimation Storyboard.TargetName="scaleTransform1" Storyboard.TargetProperty="ScaleY"  RepeatBehavior="Forever" From="1" To="2" Duration="0:0:2">
            </DoubleAnimation>
        </Storyboard>
    </ Page.Resources>
    <StackPanel>
        <Button Content="阻塞UI线程" Click="Button_Click_1"></Button>
        <Button x:Name="heightAnimationButton" Content="Height属性动画"  Click="heightAnimationButton_Click_1"></Button>
        <Button x:Name="scaleTransformAnimationButton" Content="ScaleTransform属性动画"  Click="scaleTransformAnimationButton_Click_1"></Button>
        <Rectangle Height="100" Fill="Blue" x:Name="rectangle1">
            <Rectangle.RenderTransform>
                <ScaleTransform x:Name="scaleTransform1" ></ScaleTransform>
            </Rectangle.RenderTransform>
        </Rectangle>
    </StackPanel>

MainPage.xaml.cs文件主要代码------------------------------------------------------------------------------------------------------------------    private void Button_Click_1(object sender, RoutedEventArgs e)
    {        // 阻塞UI线程2秒钟
        Task.Delay(2000).Wait();
    }    private void heightAnimationButton_Click_1(object sender, RoutedEventArgs e)
    {        // 播放改变高度属性的动画,高度有100变成200        scaleTransformStoryboard.Stop();
        heightStoryboard.Begin();
    }    private void scaleTransformAnimationButton_Click_1(object sender, RoutedEventArgs e)
    {        // 播放改变变换属性的动画,举行沿着X轴放大2倍        heightStoryboard.Stop();
        scaleTransformStoryboard.Begin();
    }

本文来源于《深入理解Windows Phone 8.1 UI控件编程》

源代码下载:http://vdisk.weibo.com/s/zt_pyrfNHoezI

欢迎关注我的微博@WP林政

WP8.1技术交流群:372552293