Flex第一步:基于ActionScript 3.0的Flex 2应用开发

6.5  自定义行为

6.5.1  了解行为的运行机制

除了组件内置的行为类型,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对象的一个好方法。

6.5.2  自定义动画效果——一个修改Iris效果的尝试

下面通过实例,修改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效果库中的动画对象,可以加深对动画对象的理解。

6.5.3  自定义触发器

触发器通常和事件相关联,要使对象具有行为属性,必须将事件和效果组合起来,形成一个触发器。

在上一节的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)拷贝到其他程序中,就可以使用了。

你可能感兴趣的:(flex)