此次AJSPlayer的开发过程中,由于将会使用到大量的,高度自定义的特效,而Flex 4中原始提供的封装特效组件(如Move、Rotate、Fade,甚至包含对应的3D特效等等)已经不能满足我们的需求了,而这个时候就需要来自定义一些effect特效。
在Flex 4里,effect特效可以应用于任意类型的对象和属性(Interpolator是这一环节中较为关键的一个东西,Adobe称之为‘补插器’,后面将会有详细介绍),这是Flex 4为我们带来的最炫目的新特性之一。
Flex4里的所有特效类都继承于Animate,而Animate同时还继承于Effect。Flex4里的特效类的层次是与Flex3大不相同,它提供了更强大,自由度更高的特效定义。当然,同时也变得更为复杂。
首先,我们以Move特效做为例子来了解一下Flex 4中的特效。
Flex 4中的Move特效继承于AnimateTransform,而AnimateTransform同时又继承于Animate。
我们通过注释可以看到这样一句话:“Animate 效果可设置各个值之间的任意属性集的动画。通过设置 motionPaths 属性,指定要设置动画的属性和值。”
motionPaths(MotionPath类型的数组)又是什么?它的字面意思是“运动路径”,查看MotionPath源码注释又看到这样的解释:“MotionPath 类定义效果的 Keyframe 对象的集合,以及要设置动画的目标上属性的名称。每个 Keyframe 对象都定义位于效果过程中某个特定时间的属性的值。这样,效果就可以通过在两个关键帧所指定的值之间进行插补来计算目标属性的值。”
从上面大致可以看出,MotionPath就是类似于flash中补间动画的东西,它的工作则是将我们给他所指定一个对象或属性名称(property),并按照分配的时间和property的初始值与结束值定义一个补间动画。通常情况下,MotionPath会假定你所指定的property是数值类型的,如果不是则需要你为它指定一个Interpolator(上文提到的补插器)。
重点来了。Interpolator是IInterpolator的一个实现,FLEX 4定义了几种Interpolator,包括NumberInterpolator、RGBInterpolator、HSBInterpolator、MultiValueInterpolator。而我们最常用的则是前面两个。animate在播放时,会自动的去调用这些插补器来计算出MotionPath的关键帧,实现动画效果,通常我们是不需要定义的。但是,有时候,比如,我们希望物体能做圆周运动,这个时候我们通常的做法是利用timer去不断的更新根据公式所计算出来的xy坐标。而自定义一个Interpolator则可以更方便的为我们解决这一问题。
看下面OvalInterpolator.as的代码:
package com.effects
{
import mx.resources.IResourceManager;
import mx.resources.ResourceManager;
import spark.effects.interpolation.IInterpolator;
import spark.effects.interpolation.NumberInterpolator;
public class OvalInterpolator extends NumberInterpolator
{
private var _width:Number;
private var _height:Number;
private var _type:String;
private var _centerPrintX:Number;
private var _centerPrintY:Number;
public function OvalInterpolator(type:String)
{
super();
_type=type;
}
/**
* @private
* Used for accessing localized Error messages.
*/
private var resourceManager:IResourceManager =
ResourceManager.getInstance();
override public function interpolate(fraction:Number, startValue:Object, endValue:Object):Object
{
if (fraction == 0)
return startValue;
else if (fraction == 1)
return endValue;
if ((startValue is Number && isNaN(Number(startValue))) ||
(endValue is Number && isNaN(Number(endValue))))
throw new Error(resourceManager.getString("sparkEffects", "cannotCalculateValue", [startValue, endValue]));
return computeInterpolate(Number(startValue) + (fraction * (Number(endValue) - Number(startValue))));
}
private function computeInterpolate(angle:Number):Number{
switch(_type)
{
case "x":
{
return (_centerPrintX+ _width - _width * Math.cos((angle) * Math.PI / 180));
}
case "y":
{
return (_centerPrintY+ _height * Math.sin((angle) * Math.PI / 180));
}
default:
{
throw new Error(resourceManager.getString("sparkEffects", "不被预知的参数类型", [_type]));
}
}
}
public function get width():Number
{
return _width;
}
public function set width(value:Number):void
{
_width = value;
}
public function get height():Number
{
return _height;
}
public function set height(value:Number):void
{
_height = value;
}
public function get type():String
{
return _type;
}
public function get centerPrintX():Number
{
return _centerPrintX;
}
public function set centerPrintX(value:Number):void
{
_centerPrintX = value;
}
public function get centerPrintY():Number
{
return _centerPrintY;
}
public function set centerPrintY(value:Number):void
{
_centerPrintY = value;
}
}
}
其实很容易就明白,interpolate的三个参数分别为:进度的百分百,初始值,结束值。MotionPath在调用这一接口方法的时候,会自动根据动画的播放进度传入适当的值,我们需要做的则是提供根据这三个参数所计算的进度返回的适当值。
接着,我们只需要将这个插补器指定给我们需要的Animate的MotionPath就行。
而前面所提到AnimateTransform的工作其实就是为我们定义并封装了motionPaths,并且按照根据需要实现特效的属性指定对应类别的插补器,相当于为其做了一个“归类”的操作。Flex 4中的所有封装的顶层特效,都有与AnimateTransform类似,但是管理着不同类型或种类的对象及属性的父类,他们都会将其做为一个实例赋给Animate,以实现严格的归类特效。
也许文字描述过于抽象或枯燥,下面看完整demo代码:
CustomAnimateEffect.as
package com.effects
{
import mx.controls.Alert;
import mx.effects.IEffectInstance;
import mx.events.EffectEvent;
import spark.components.mediaClasses.VolumeBar;
import spark.effects.Animate;
import spark.effects.animation.MotionPath;
import spark.effects.animation.SimpleMotionPath;
import spark.effects.interpolation.IInterpolator;
public class CustomAnimateEffect extends Animate {
private var propertys:Vector.<String>=new Vector.<String>();
public function CustomAnimateEffect (target:Object=null) {
super(target);
motionPaths = new Vector.<MotionPath>();
}
public function addMotionPath(property:String,valueFrom:Object,valueTo:Object,interpolator:IInterpolator=null):void{
propertys.push(property);
var motionPath:SimpleMotionPath = new SimpleMotionPath(property);
motionPath.valueFrom = valueFrom;
motionPath.valueTo = valueTo;
motionPath.property=property;
if(interpolator){
motionPath.interpolator=interpolator;
}
motionPaths.push(motionPath);
}
}
}
demo.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="955" minHeight="600"
creationComplete="creationCompleteHandler(event)" xmlns:effects="com.effects.*">
<fx:Script>
<![CDATA[
import com.effects.OvalInterpolator;
import mx.events.FlexEvent;
protected function creationCompleteHandler(event:FlexEvent):void
{
var ovalInterpolatorx:OvalInterpolator = new OvalInterpolator("x");
ovalInterpolatorx.width=150;
ovalInterpolatorx.centerPrintX=300;
var ovalInterpolatory:OvalInterpolator = new OvalInterpolator("y");
ovalInterpolatory.height=150;
ovalInterpolatory.centerPrintY=300;
customAnimateEffect.addMotionPath("x",0,360,ovalInterpolatorx);
customAnimateEffect.addMotionPath("y",0,360,ovalInterpolatory);
customAnimateEffect.end();
}
]]>
</fx:Script>
<fx:Declarations>
<effects:CustomAnimateEffect duration="1000" id="customAnimateEffect" target="{canv}"/>
</fx:Declarations>
<mx:Canvas id="canv" x="155" y="37" width="303" height="279" backgroundColor="#632A2A" alpha=".1"/>
<s:Button label="Click to fade in the button below" click="customAnimateEffect.play()" />
</s:Application>