除了组件内置的行为类型,Flex还支持用户自定义行为。由于行为对象分触发器和动画效果两部分,因此,行为自定义也分两部分,包括自定义触发器和自定义动画效果。
在创建动画效果之前,先来了解动画效果的运作方式。每个动画效果由两个对象来实现:一个生 成(factory)类和一个实例(instance)类,factory对象用来处理事件,控制动画,而instance对象则 包含了动画效果的逻辑实现。当动画开始播放时,factory对象将创建一个instance对象来执行动画效 果,一旦动画结束,这个对象会被销毁。如果有多个目标对象,会创建多个instance对象,使得一个 对象对应一个instance。factory对象名和动画效果名称相同,而instance对象名是动画效果名再加上后缀“Instance”。
例如在上一节中:
这句代码定义了一个Move对象,当动画播放时Move对象会生成一个MoveInstance对象,动画播放完毕,MoveInstance被删除。
下面列出了mx.effects中核心对象的层级关系,如图6.14所示。
图6.14 (a)中所示的是所有生成类的层级关系,图6.14(b)中的则是所有实例类的层级关系。在创建自定义动画效果时,只需选择上面的对象进行扩展即可。我们可以扩展Effect类,也可以扩展Effect的子类(如TweenEffect)。其中MaskEffect是遮罩类型,TweenEffect是渐变类型,CompositeEffect是复合类型。这里又以TweenEffect的使用最为广泛,之前讲到的AnimateProperty、 Blur、Dissolve、Fade、Glow、Move、Pause、Resize、Rotate、Zoom等都是TweenEffect的子类。复合类型并不是新类型,它只是其他动画效果的集合,Parallel和Sequence就是它的子类。
扩展Effect和EffectInstance对象时,必须覆盖父类的部分方法或属性。
(a) (b)
图6.14
表6.3列出了生成类中必须定义的属性和方法。
表6.3
函数名或属性 |
事 件 描 述 |
构造函数 |
必需的。类的构造函数,必须调用super()方法来调用父类的构造函数进行初始化,并且必须接受一个参数来指定目标对象。 |
initInstance() |
必需的。将生成类的属性传到实例对象中,这个方法由Effect对象内部调用,不在外部使用。 另外,覆盖的函数体中必须执行函数:super.initInstance()。 |
getAffectedProperties() |
必需的。返回一个字符串数组,包括目标对象被改变的属性名。如果没有改变,则返回空 数组。 |
effectStartHandler() |
可选的。该函数在实例开始播放时被调用,如覆盖,需要执行super()方法。 |
effectEndHandler() |
可选的。该函数在实例结束播放时被调用,如覆盖,需要执行super()方法。 |
其他属性和方法 |
可选的。自定义其他接口。 |
表6.4是实例类中必须定义的属性和方法。
表6.4
函数名或属性 |
事 件 描 述 |
构造函数 |
必需的。类的构造函数,必须调用super()方法来调用父类的构造函数进行初始化,有且必须接受一个参数来指定目标对象。 |
play() |
必需的。控制动画的播放,覆盖的函数体中必须执行super.play()。 |
end() |
可选的。结束动画播放。 |
initEffect() |
可选的。该函数在动画效果被激活时被调用。 |
onTweenUpdate() |
必需的。如果继承于TweenEffectInstance必须覆盖此方法。该函数在动画播放过程中,会被反复 调用。 |
onTweenEnd |
可选的。如果继承于TweenEffectInstance才可以覆盖此方法。该函数在动画播放结束时会被调用,函数体中必须执行super.onTweenEnd()。 |
其他属性和方法 |
可选的。自定义其他接口。 |
通常情况下,我们没有必要直接扩展Effect和EffectInstance,更方便的做法是扩展它们的子类。例如TweenEffect和 TweenEffectInstance,在这些子类中已经实现了表中的核心方法,这会省很多事。另外,阅读Effect子类的源代码,是学习 Effect对象的一个好方法。
下面通过实例,修改Flex的Iris效果,来理解如何创建自定义的动画效果。
在修改前,让我们先来看看Iris类的代码。
首先,进入Flex SDK目录下的Flex SDK 2\frameworks\source源代码文件夹,再选择mx\effects文件夹,打开Iris.as和effectClasses文件夹中的 IrisInstance.as两个文件。下面是这两个文件的代码,这里省略了文件中的版权说明和注释文字:
Iris.as:
package mx.effects{
//导入Iris的实例类
import mx.effects.effectClasses.IrisInstance;
//扩展MaskEffect
public class Iris extends MaskEffect{
//包含Flex组件版本说明文件
include "../core/Version.as";
//构造函数
public function Iris(target:Object = null){
//调用父类构造函数
super(target);
//指定实例类型
instanceClass = IrisInstance;
}
}
}
这个类的代码很少,最关键的代码只有一行,就是:instanceClass = IrisInstance,表示Iris动画对应的动画实例对象是IrisInstance对象。
IrisInstance对象实现了动画的主体功能,下面是文件IrisInstance.as的内容:
package mx.effects.effectClasses{
import mx.controls.SWFLoader;
//扩展MaskEffectInstance
public class IrisInstance extends MaskEffectInstance{
include "../../core/Version.as";
//构造函数
public function IrisInstance(target:Object){
super(target);
}
//覆盖父类的方法
override protected function initMaskEffect():void{
//调用父类的同名方法
super.initMaskEffect();
//得到目标的实际尺寸
//targetVisualBounds对象包括了目标可视区域的长和宽
var targetWidth:Number = targetVisualBounds.width / Math.abs(target.scaleX);
var targetHeight:Number = targetVisualBounds.height / Math.abs(target.scaleY);
//如果目标是SWFLoader对象,则计算加载内容的尺寸,而不是目标本身的尺寸
if (target is SWFLoader) {
targetWidth = target.contentWidth;
targetHeight = target.contentHeight;
}
//如果showTarget为true,表示显示目标
if (showTarget){
//定义x、y方向的起始放缩值
scaleXFrom = 0;
scaleYFrom = 0;
scaleXTo = 1;
scaleYTo = 1;
//目标的起始位置
xFrom = targetWidth / 2 + targetVisualBounds.x;
yFrom = targetHeight / 2 + targetVisualBounds.y;
//确定最后的坐标
xTo = targetVisualBounds.x;
yTo = targetVisualBounds.y;
}else{
//如果是隐藏目标
scaleXFrom = 1;
scaleYFrom = 1;
scaleXTo = 0;
scaleYTo = 0;
//目标的起始位置
xFrom = targetVisualBounds.x;
yFrom = targetVisualBounds.y;
//确定最后的坐标
xTo = targetWidth / 2 + targetVisualBounds.x;
yTo = targetHeight / 2 + targetVisualBounds.y;
}
}
}
}
IrisInstance中只有一个核心函数initMaskEffect,主要用来计算动画的各种参数,包括起始值、位置、坐标点等等。
相对其他动画效果而言,Iris效果的这两个类的代码较少,适合我们来学习和修改。
默认状态下Iris采用的遮罩层是矩形的,且动画始终以组件为中心进行运作,我们的目的是将遮罩层修改为圆形,且能选择动画播放时的中心点。
接下来,创建项目NewIris,并分别创建NewIrisEffect.as和NewIrisInstance.as两个类,在Iris两个源文件的基础上进行修改,新文件的内容如下:
源代码:NewIrisEffect.as
package tree{
//导入代码中使用的对象
import mx.effects.MaskEffect;
import mx.effects.EffectInstance;
import mx.controls.SWFLoader;
import flash.display.Shape;
import mx.core.FlexShape;
import flash.display.Graphics;
import flash.geom.Rectangle;
//保持原来类的结构不变,继续扩展MaskEffect对象
public class NewIrisEffect extends MaskEffect {
//表明动画采用的起始点的类型
public var position:String;
//构造函数
function NewIrisEffect(target:Object = null){
super(target);
//定义实例类型为NewIrisInstance,代替原来的IrisInstance
//NewIrisInstance是我们新建的对象
instanceClass = NewIrisInstance;
//定义新的createMaskFunction,此函数用于绘制遮罩层
this.createMaskFunction = createLargeMask;
}
//此函数用来绘制遮罩层
//函数原形来自mx.effects.effectClasses. MaskEffectInstance对象的defaultCreateMask
函数
public function createLargeMask(targ:Object, bounds:Rectangle):Shape{
//绘制前,首先获得目标的尺寸,来确定遮罩层的尺寸
//默认情况下,遮罩层和目标对象的尺寸一致
var targetWidth:Number = bounds.width / Math.abs(targ.scaleX);
var targetHeight:Number = bounds.height / Math.abs(targ.scaleY);
//
if (targ is SWFLoader) {
// 如果是加载外部内容,则以外部内容的尺寸为准
targ.validateDisplayList();
if (targ.content){
targetWidth = targ.contentWidth;
targetHeight = targ.contentHeight;
}
}
//创建遮罩层的表现对象
var newMask:Shape = new FlexShape();
var g:Graphics = newMask.graphics;
//设定填充色
g.beginFill(0xFFFF00);
//绘制椭圆,drawEllipse是Graphics对象的绘制方法之一
g.drawEllipse(0,0,targetWidth,targetHeight);
//默认情况下绘制矩形,这里将这句代码注释掉
//g.drawRect(0, 0, targetWidth, targetHeight);
g.endFill();
//判断是否旋转
if (target.rotation == 0) {
//如果目标没有旋转
newMask.width = targetWidth;
newMask.height = targetHeight;
}else{
//如果有旋转,重新计算遮罩层的大小
//得到角度的弧度数
var angle:Number = targ.rotation * Math.PI / 180;
var sin:Number = Math.sin(angle);
var cos:Number = Math.cos(angle);
//计算出新的尺寸
newMask.width = Math.abs(targetWidth * cos - targetHeight * sin);
newMask.height = Math.abs(targetWidth * sin + targetHeight * cos);
}
return newMask;
}
//覆盖父类的initInstance方法。
//将生成类的属性传到实例对象中,这个方法在对象内部被调用
override protected function initInstance(instance:EffectInstance):void {
//必须调用父类的同名方法
super.initInstance(instance);
//创建新的实例
var maskEffectInstance:NewIrisInstance = NewIrisInstance(instance);
//传递参数给实例
maskEffectInstance.showTarget = showTarget;
maskEffectInstance.xFrom = xFrom;
maskEffectInstance.yFrom = yFrom;
maskEffectInstance.xTo = xTo;
maskEffectInstance.yTo = yTo;
maskEffectInstance.scaleXFrom = scaleXFrom;
maskEffectInstance.scaleXTo = scaleXTo;
maskEffectInstance.scaleYFrom = scaleYFrom;
maskEffectInstance.scaleYTo = scaleYTo;
//这是新添加的参数,用来确定动画执行时的初始位置
//是我们实现新的Iris效果的关键
maskEffectInstance.position = position
}
}
}
在NewIrisEffect类中,我们修改了Iris类的代码,主要包括两点:
(1)覆盖了createMaskFunction函数,该函数主要用来绘制遮罩层。createMaskFunction函数原形来自 mx.effects.effectClasses.MaskEffectInstance对象的defaultCreateMask函数,这里只是做了一点小小的修改,在绘制矩形的位置:
//默认情况下绘制矩形
//g.drawRect(0, 0, targetWidth, targetHeight);
修改为:
g.drawEllipse(0,0,targetWidth,targetHeight);
其中g是Graphics类型,drawEllipse和drawRect都是Graphics对象的绘图函数,drawEllipse用来绘制椭圆,drawRect用来绘制矩形。
createMaskFunction函数的其他部分和defaultCreateMask函数相同,读者可以参照注释阅读其他部分的代码,这里并不需要完全理解这些代码的含义。
(2)覆盖了initInstance方法,用来创建新的NewIrisInstance对象。initInstance方法是Effect对象的核心方法,用来创建动画实例,因为我们修改了实例对象,所以也要修改initInstance方法。在initInstance方法中,必须调用父类的同名方法。同时,要创建新的动画实例,并给实例对象传递参数:
var maskEffectInstance:NewIrisInstance = NewIrisInstance(instance);
…
maskEffectInstance.position = position;
NewIrisInstance是新创建的实例对象,其position属性是我们添加的一个属性,用来标示动画的参照位置,这个参数在后面要使用到。
在NewIrisEffect的构造函数中,覆盖了原来的两个属性:
//定义实例类型为NewIrisInstance,代替原来的IrisInstance
instanceClass = NewIrisInstance;
//定义新的createMaskFunction,此函数用于绘制遮罩层
this.createMaskFunction = createLargeMask;
这样,整个NewIrisEffect对象就完成了,接着是NewIrisInstance.as的源代码:
package tree{
import mx.effects.effectClasses.MaskEffectInstance
import mx.controls.SWFLoader;
public class NewIrisInstance extends MaskEffectInstance {
//新添加的参数,用来确定动画播放时的初始位置
public var position:String;
//构造函数
public function NewIrisInstance(target:Object) {
super(target);
}
//* 覆盖父类方法,initMaskEffect主要是初始化遮罩动画的信息
//此函数取自IrisInstance的同名函数
override protected function initMaskEffect():void{
//调用父类的同名方法
super.initMaskEffect();
//计算出目标的尺寸
//targetVisualBounds包括了目标可视区域的长、宽
var targetWidth:Number = targetVisualBounds.width / Math.abs(target.scaleX);
var targetHeight:Number = targetVisualBounds.height / Math.abs(target.scaleY);
//如果是SWFLoader,以记载的内容的尺寸为准
if (target is SWFLoader){
targetWidth = target.contentWidth;
targetHeight = target.contentHeight;
}
//如果是让目标出现
if (showTarget){
scaleXFrom = 0;
scaleYFrom = 0;
scaleXTo = 1;
scaleYTo = 1;
//依据position属性来决定起点的位置
//如果中心是左上角
if(position == "leftTop"){
//从左上角开始
//targetVisualBounds还包括了目标可视区域的坐标位置
xFrom = targetVisualBounds.x;
yFrom = targetVisualBounds.y;
}else if(position == "rightBottom"){
//如果中心是右下角
//从右上角开始
xFrom = targetWidth+ targetVisualBounds.x;
yFrom = targetHeight + targetVisualBounds.y;
}else{
//默认从中心开始,保持原来的计算方式不变
xFrom = targetWidth / 2 + targetVisualBounds.x;
yFrom = targetHeight / 2 + targetVisualBounds.y;
}
//终点位置
xTo = targetVisualBounds.x;
yTo = targetVisualBounds.y;
}else{
scaleXFrom = 1;
scaleYFrom = 1;
scaleXTo = 0;
scaleYTo = 0;
//消失动画的起点
xFrom = targetVisualBounds.x;
yFrom = targetVisualBounds.y;
//再计算一遍新的位置
if(position == "leftTop"){
xTo = targetVisualBounds.x;
yTo = targetVisualBounds.y;
}else if(position == "rightBottom"){
xTo = targetWidth + targetVisualBounds.x;
yTo = targetHeight + targetVisualBounds.y;
}else{
xTo = targetWidth / 2 + targetVisualBounds.x;
yTo = targetHeight / 2 + targetVisualBounds.y;
}
}
}
}
}
NewIrisInstance沿用了IrisInstance对象的代码,只不过在initMaskEffect方法中,加入了判断动画参照点的代码,用来实现我们的新功能:
if(position == "leftTop"){
//从左上角开始
//targetVisualBounds还包括了目标可视区域的坐标位置
xFrom = targetVisualBounds.x;
yFrom = targetVisualBounds.y;
}else if(position == "rightBottom"){
//从右下角开始
xFrom = targetWidth+ targetVisualBounds.x;
yFrom = targetHeight + targetVisualBounds.y;
}else{
//默认从中心开始,保持原来的计算方式不变
xFrom = targetWidth / 2 + targetVisualBounds.x;
yFrom = targetHeight / 2 + targetVisualBounds.y;
}
position 属性存放了参照点位置,这个参数是在NewIrisEffect对象的initInstance函数中被传递过来的。 targetVisualBounds中包括了目标对象可视区域的坐标位置,利用这个对象,我们可以很快计算出参照点的坐标。这里一共添加了两个位置: leftTop和rightBottom,分别表示左上角和右下角。
完成了动画对象的所有代码后,下面就尝试将它们运用在实例中。在NewIris主程序中插入相应代码,代码如下:
layout="absolute"> "{myIris}" />
在代码中,使用MXML语句创建了一个NewIrisEffect对象:
NewIrisEffect的用法和其他动画对象的用法没什么差别,这里多了一个position属性,用来指定动画的参照点方式。
Image控件中,仍然是使用showEffect和hideEffect来指定动画对象。
按下Ctrl+F11快捷键运行程序。点击按钮,运行的效果如图6.15所示。
至此,完成了一个简单的动画效果。
通过修改Flex效果库中的动画对象,可以加深对动画对象的理解。
触发器通常和事件相关联,要使对象具有行为属性,必须将事件和效果组合起来,形成一个触发器。
在上一节的NewIris程序的基础上,我们继续完善自定义的动画效果。下面创建一个新的用户组件ImgBox,放在tree文件夹中,编写代码如下。
源代码:tree/ImgBox.mxml
[Event(name="hideImage", type="flash.events.Event")]
[Event(name="showImage", type="flash.events.Event")]
[Effect(name="hideImageEffect", event="hideImage")]
[Effect(name="showImageEffect", event="showImage")]
//鼠标按下事件
internal function doDown():void{
dispatchEvent(new Event("hideImage")); //派发事件
}
internal function doUp():void{
dispatchEvent(new Event("showImage")); //派发事件
}
]]>
在代码中,利用ImgBox的showImageEffect和hideImageEffect触发器,给组件分别定义了两个NewIrisEffect效果。showImageEffect和hideImageEffect就好比是组件的两个属性,可以随意修改。
运行程序,在图片上按下鼠标不动,观看动画播放,如图6.16所示。松开鼠标,另一个动画开始 播放。
鼠标按下时 鼠标松开后
图6.16
至此,我们完成一个完整的自定义行为。这个行为效果和程序并没有直接联系,可以被重复使用。只需要把tree目录(包括NewIrisEffect.as和NewIrisInstance.as)拷贝到其他程序中,就可以使用了。