线性动画的一个缺点是,它通常让人觉得很机械且不能够自然。相比而言,高级的用户界面具有模拟真实世界系统的动画效果。例如,可能使用具有触觉的下压按钮,当单击时按钮快速弹回,但是当没有进行操作时它们会慢慢地停下来,创建真正移动的错觉。或者,可能使用类似windows操作系统的最大化和最小化效果,当窗口解决最终尺寸时窗口扩展或收缩的速度会加速。这些细节十分细微,当它们的实现比较完美时可能不会注意到它们。然而,几乎总会注意到,粗糙的缺少这些更细微特征的动画会给人留下笨拙的印象。

  改进动画并创建更趋自然的动画的秘诀是改变变化速率。不是创建以固定不变的速率改变的属性的动画,而是需要设计根据某种方式加速或减速的动画。wpf提供了几种选择。基于帧的动画和关键帧动画,这两种技术都提供了更精细地控制动画的能力。但实现更趋自然的动画的最简单方法是使用预置的缓动函数(easing function)。

  当使用缓动函数时,仍可通过指定开始和结束属性值以常规的方式定义动画。但为了附加这些细节,需要添加预先编写好的修改动画过场的数学函数,使动画在不同的点加速或减速。

一、使用缓动函数

  动画缓动的最大优点是,相对于其他方法,如基于帧的动画和关键帧动画,这种方法需要的工作少很多。为使用动画缓动,使用某个缓动函数类(继承自easingfunctionbase的类)的实例设置动画对象的easingfunction属性。通常需要设置缓动函数的几个属性,并且为了得到所希望的效果,可能必须使用不同的设置,但不需要编写代码并且只需很少的xaml。

  例如,分析下面给出的两个动画,这两个动画用于按钮。当用户将鼠标移到按钮上时,使用一小段代码调用growstoryboard动画,将按钮拉伸到400单位。当用户移动鼠标使其离开按钮时,按钮收缩到其正常尺寸。

<storyboard x:name="growstoryboard">
   <doubleanimation storyboard.targetname="cmdgrow" storyboard.targetproperty="width"
             to="400" duration="0:0:1.5"></doubleanimation>
</storyboard>
<storyboard x:name="revertstoryboard">
    <doubleanimation storyboard.targetname="cmdgrow" storyboard.targetproperty="width"
            duration="0:0:3"></doubleanimation>
</storyboard>

  现在,动画使用线性插值,这意味着按钮以恒定的机械性的速度增长和收缩。为得到更趋自然的效果,可使用缓动函数。下面的示例添加了名为elasticease的缓动函数。最终效果是按钮弹跳出其完整宽度,然后迅速弹回一点,接着在此摆动超出其完整尺寸(但比上一次稍少一点),再以稍小的幅度迅速弹回,等等,随着运动的减弱不断地重复这一跳动模式。之后逐渐进入缓和的10此振荡。oscillations属性控制最终跳动的次数。elasticease类提供了另一个在该例中没有使用的属性:springiness。该属性的值越大,后续的每个振荡静止得越快(默认值是3)。

<storyboard x:name="growstoryboard">
    <doubleanimation storyboard.targetname="cmdgrow" storyboard.targetproperty="width"
                                     to="400" duration="0:0:1.5">
           <doubleanimation.easingfunction>
                  <elasticease oscillations="10" easingmode="easeout"></elasticease>
            </doubleanimation.easingfunction>
      </doubleanimation>
</storyboard>

  为真正理解该标记和前面缓动函数的示例之间的区别,需要试一下该动画。变化是显著的。仅时候用一行xaml,就将一个简单的动画从业务的效果修改为精致美观的效果,在专业的应用程序中会感觉到这种精致效果。

二、在动画开始时应用缓动与动画结束时应用缓动

  在继续分析不同的缓动函数前,理解缓动函数的应用时机很很重要的。所有缓动函数类都继承自easingfunctionbase类,并且继承了easingmode属性。该属性具有三个可能值:easein(该值意味着在动画开始时应用缓动效果)、easeout(该值意味着在动画结束时应用缓动效果)、easeinout(该值意味着在动画开始和结束时应用缓动效果——将easein用于动画的前半部分,将easeout用于动画的后半部分)。

  在上面的示例中,growstoryboard中的动画使用easeout模式。因此,逐渐减弱的跳动序列发生于动画的末尾。

  如果将elasticease函数的缓动模式切换为easein,跳动将在动画的开始部分发生。按钮手势使其宽度比开始值更小一点,然后扩展宽度使其超过开始值,继而再稍多地收缩回一点,持续这种模式以逐渐地增加振荡直到自由振荡并扩展剩余的部分(使用elasticease.osicillations属性控制振荡次数)。

  最后,easeinout模式创建更新颖的效果,在动画的前半部分是振荡动画的开始,接下来在动画的后半部分是振荡动画的结束。

三、缓动函数类

  wpf提供了11个缓动函数类,所有这些类都位于熟悉的system.windows.media.animation名称控件中。下表描述了所有缓动函数类,并列出了它们的重要属性。请记住,每个缓动函数类还提供了easingmode属性,用于控制是影响动画的开始(easein)、是影响动画的结束(easeout)还是同时影响动画的开始和结束(easeinout)。

表 缓动函数

 名    称    说     明    属    性
backease 当使用easein模式应用该缓动函数时,在动画开始之前来回动画。当使用easeout模式应用该缓动函数时,允许动画稍微超越然后拉回 amplitude属性决定了拉回和超越的量。默认值是1,可减少该属性值(大于0的任何值)以缩减效果,或增加该属性值以放大效果
elasticease 当使用easeout模式应用缓动函数时,使动画超越其最大值并前后摆动,逐渐减慢。当使用easein模式应用该缓动函数时,动画在其开始值周围前后摆动,逐渐增加 oscillations属性控制动画前后摆动的次数(默认值是3),springiness属性控制振荡增加或减弱的速度(默认值是3)
bounceease 执行与elasticease缓动函数类似的效果,只是弹跳永远不会超越初始值或最终值 bounce属性控制动画回跳的次数(默认值是2),bounciness属性决定弹跳增加或减弱的速度(默认值是2)
circleease 使用圆函数加速(使用easein模式)或减速(使用easeout模式)动画
cubicease 使用基于时间立方的函数加速(使用easein模式)动画。其效果与circleease类似,但是加速过程更缓和
quadraticease 使用基于时间平分的函数加速(使用easein模式)或减速(使用easeout模式)动画。效果与cubicease类似,但加速过程更缓和
quarticease 使用基于时间4次方的函数加速(使用easein模式)或减速(使用easeout模式)动画。效果和cubicease以及quadraticease类似,但加速过程更明显
quinticease 使用基于时间5次方的函数加速(使用easein模式)或减速(使用easeout模式)动画。效果和cubicease、quadraticease以及quarticease类似,但是加速过程更明显
sineease 使用包含正弦计算的函数加速(使用easein模式)或减速(使用easeout模式)动画。加速非常缓和,并且相对于其他各种缓动函数更接近线性插值
powerease 使用幂函数f(t)=t^p加速(使用easein模式)或减速(使用easeout模式)动画。根据为指数p使用的值,可复制cubic、quadraticease、quarticease以及quinticease power属性用于设置公式中的指数。将该属性设置为2会复制quadraticease的效果,设置为3会复制cubicease的效果。设置为4会复制quarticease的效果。设置为5会复制quinticease效果,或选择其他不同值,默认值是2
exponentialease 使用指数函数f(t)=(e(at)-1)/(e(a)-1)加速(使用easein模式)或减速(使用easeout模式)动画 exponent属性用于设置指数(默认值是2)

  许多缓动函数提供了类似但隐约不同的效果。为成功地使用动画缓动,需要决定使用哪个缓动函数,以及如何进行配置。通常,这个过程需要一点试错的体验。有两个资源可提供帮助。

  首先,wpf文档为每个缓动函数的行为提供了插图示例,显示动画如何随着时间修改属性值。查看这些插图是理解缓动函数作用的好方法。

  其次,microsoft提供了几个范例程序,可使用这些范例播放不同的缓动函数,并尝试不同的属性值。最方便的范例之一是silverlight应用程序。

四、创建自定义缓动函数

  通过从easingfunctionbase继承自己的类,并重载easeincore()和createinstancecore()方法,可创建自定义缓动效果。这是一个非常专业的技术,因为大部分开发人员能通过配置标准的缓动函数来获得所希望的效果。然而,如果确实决定创建自定义缓动函数,将发现该过出奇简单。

  需要编写的几乎所有逻辑都在easeincore()方法中运行。该方法接受一个规范化的时间值——本质上,是表示动画进度的从0到1之间的值。当动画开始时,规范化得时间值是0。它从该点开始增加,直到在动画结束点达到1。

protected override double easeincore(double normalizedtime)
{...}

  在动画运行期间,每次更新动画的值时wpf都会调用easeincore()方法。确切的调用频率取决于动画的帧率,但可以预期每秒调用easeincore()方法的次数接近60。

  为执行缓动,easeincore()方法采用规范化的时间值,并以某种方式对其进行调整。easeincore()方法返回的调整后的值,随后被用于调整动画的进度。例如,如果easeincore()方法返回0,动画被返回到其开始点。如果easeincore()方法返回1,动画跳到其结束点。然而,easeincore()方法的返回值并不局限于这一范围——例如,可返回1.5以使动画过渡运行自身50%。已经看到过用于缓动函数(如elasticease)的这类效果。

  下面给出的easeincore()方法版本根本不执行任何工作。该版本返回规范化的时间值,意味着动画将均匀展开,就像是没有缓动。

protected override double easeincore(double normalizedtime)
{
   return normalizedtime;     
}

  下面的easeincore()方法版本通过计算规范化时间值得立方,复制cubicease函数的效果。因为规范化的时间值是小数,其立方值是更小的小数;所以该方法的效果是最初减慢动作动画,并当规范化的时间值(及其立方值)解决与1时导致动画加速。

protected override double easeincore(double normalizedtime)
        {
            return math.pow(normalizedtime, 3);
        }

  最后,下面是一个执行更有趣内容的自定义缓动函数——以一定的随机量便宜规范化的时间值,导致分散的抖动效果。可使用提供的jitter依赖性属性(在一个较小的范围内)调整抖动的幅度,该属性接受从0到2000之间的数值。

public class randomjitterease : easingfunctionbase
    {

        // store a random number generator.
        private random rand = new random();

        protected override double easeincore(double normalizedtime)
        {
            //to see the values add code like this:
            //system.diagnostics.debug.writeline(...);

            // make sure there's no jitter in the final value.
            if (normalizedtime == 1) return 1;

            // offset the value by a random amount.
            return math.abs(normalizedtime - (double)rand.next(0, 10) / (2010 - jitter));
        }

        public int jitter
        {
            get { return (int)getvalue(jitterproperty); }
            set { setvalue(jitterproperty, value); }
        }

        public static readonly dependencyproperty jitterproperty =
            dependencyproperty.register("jitter", typeof(int), typeof(randomjitterease),
            new uipropertymetadata(1000), new validatevaluecallback(validatejitter));

        private static bool validatejitter(object value)
        {
            int jittervalue = (int)value;
            return ((jittervalue <= 2000) && (jittervalue >= 0));
        }

        // this required override simply provides a live instance of your easing function.
        protected override freezable createinstancecore()
        {
            return new randomjitterease();
        }
    }

下面是缓动函数在xaml中使用的示例:

<window x:class="animation.customeasingfunction"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:animation"
        title="customeasingfunction" height="300" width="600">
    <window.triggers>
        <eventtrigger routedevent="window.loaded">
            <eventtrigger.actions>
                <beginstoryboard>
                    <storyboard>
                        <doubleanimation
            storyboard.targetname="ellipse1" storyboard.targetproperty="(canvas.left)"
            to="500" duration="0:0:10">
                        </doubleanimation>
                        <doubleanimation
            storyboard.targetname="ellipse2" storyboard.targetproperty="(canvas.left)"
            to="500" duration="0:0:10">
                            <doubleanimation.easingfunction>
                                <local:randomjitterease easingmode="easein" jitter="1000"></local:randomjitterease>
                            </doubleanimation.easingfunction>
                        </doubleanimation>
                    </storyboard>
                </beginstoryboard>
            </eventtrigger.actions>
        </eventtrigger>
    </window.triggers>
    <canvas margin="10">
        <ellipse name="ellipse1" canvas.left="0" fill="red" width="20" height="20"></ellipse>

        <ellipse name="ellipse2" canvas.top="100" canvas.left="0" fill="red" width="20" height="20"></ellipse>
    </canvas>
</window>

  效果图如下所示,可以看到上面的圆圈平滑向右移动,下面的圆圈来回缓动向右移动: