一、项目验证
先看项目成绩,下面是一个风速控件(由于是为客户开发的,就不提供swf了,只提供截图):
其中有多项配置值可通过js设置,见下:
var config3 = { width:300, // 控件宽 height:300, // 控件高 showAnimation:true, // 是否显示动画 animateSeconds:1.5, // 动画速度,单位为秒 title:"风速(m/s)", // 标题 titleFontSize:14, // 标题字体大小 value:132.9, // 控件显示值,单位:角度 valueFontSize:12, // 控件显示值的字体大小 titleMiddleOffset:-35, // 标题的纵坐标离圆心的距离,负代表在上 valueMiddleOffset:20, // 内容框的纵坐标离圆心的距离,负代表在上 innerRadius:90, // 內圆半径 innerThickness:8, // 內圆厚度 minScaleValue:0, // 最小刻度值 maxScaleValue:140, // 最大刻度值 gapAngles:38, // 空缺扇形的角度 scaleGrids:14, // 刻度的格数 // 刻度盘的染色配置 masklist:"{min:0,max:25,color:0x66b266; min:80,max:140,color:0xfc6464}" };
swfobject.embedSWF('SectorMeter.swf', 'demo3', "300", "300", "9.0.0", {}, config3);
还可以动态改变控件值(有动画效果):
thisMovie("demo3").setValue(xxx);
这控件看起来蛮复杂的,实际上抛开公共代码,具体的编码量总计为254行!swf文件大小为25k(比用flash开发的会略大)。充分证明了只用Flex的思想和工具类的Flash开发的可行性和有效性。需要说明的是,项目类型需要设定成 MX only。
二、3个基础类
先介绍3个基础类:BaseComponent、BaseContainer和Application。BaseComponent.as 和 BaseContainer.as 绝大部分是沿用了 的Component.as 和 Container.as代码,我只是加了个removeAllChildren方法:
public function removeAllChildren():void { while( this.numChildren > 0) { this.removeChildAt(0); } }
接着是 Application.as 类,也是使用了国外文章的方案(很抱歉,原文地址忘记了):
package { import flash.display.DisplayObject; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; [DefaultProperty( "children" )] [Bindable] public class Application extends Sprite { protected var _width:Number = 0; protected var _height:Number = 0; public function Application() { super(); x = 0; y = 0; if(stage != null) { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; } addEventListener(Event.ENTER_FRAME, onInvalidate); } private var _children:Vector.<DisplayObject>; private var childrenChanged:Boolean = false; public function get children():Vector.<DisplayObject> { return _children; } public function set children( value:Vector.<DisplayObject> ):void { if ( _children != value ) { _children = value; childrenChanged = true; invalidate(); } } protected function invalidate():void { addEventListener(Event.ENTER_FRAME, onInvalidate); } protected function onInvalidate(event:Event) : void { if ( childrenChanged ) { while ( numChildren > 0 ) { removeChildAt( 0 ); } for each ( var child:DisplayObject in children ) { addChild( child ); } childrenChanged = false; } removeEventListener(Event.ENTER_FRAME, onInvalidate); } override public function set width(w:Number):void { _width = w; invalidate(); dispatchEvent(new Event(Event.RESIZE)); }
override public function get width():Number { return _width; } override public function set height(h:Number):void { _height = h; invalidate(); dispatchEvent(new Event(Event.RESIZE)); }
override public function get height():Number { return _height; } override public function set x(value:Number):void { super.x = Math.round(value); } override public function set y(value:Number):void { super.y = Math.round(value); } } }
这样一来,就可以在MXML中进行布局了。
三、第一个程序:雅美蝶!
下面,基于上面的三个类,写出第一个程序:Hello World。当然,为了与时俱进,这里不能叫Hello World,叫雅美蝶!
由于在Flash Builder开发环境中,即使引用了包含fl的库文件,也不会提示其中的控件。为了方便以及改变原控件的一些不合理行为,我们可以继承fl控件,如用Label继承TextField类:
package { import flash.text.TextField; import flash.text.TextFormat;
public class Label extends TextField { public override function set defaultTextFormat(format:TextFormat):void { if(this.defaultTextFormat != format) { super.defaultTextFormat = format; this.text = this.text; } } } }
因为 TextField 需要设定 defaultTextFormat 在先,设定 text 在后,不然 text 是不会改变的,在MXML中十分难用。这里重写defaultTextFormat 的 setter方法。
TextFormat类在MXML下也十分难用,这里我们也继承下它:
package { import flash.text.TextFormat;
public class TextFormatter extends TextFormat { public function TextFormatter() { super(); this.font = "宋体"; } public function setBold(val:Boolean = true):TextFormatter { this.bold = true; return this; } public function setAlign(val:String = "left"):TextFormatter { this.align = val; return this; } public function setSize(val:int = 10):TextFormatter { this.size = val; return this; } } }
这样一改,就可以很方便的利用IDE的智能提示了。
下面,写出第一个程序:雅美蝶!
<?xml version="1.0" encoding="utf-8"?> <local:Application xmlns:fx=" xmlns:local="*" width="500" height="100" xmlns:d="flash.display.*" > <local:Label x="30" y="30" text="雅美蝶!" defaultTextFormat="{new TextFormatter().setSize(20)}" width="100" /> </local:Application>
看看运行结果:
程序大小:6033Byte!
下面我们让它动起来。虽然可以自己写动画代码,但为了省事,还是直接调用大名鼎鼎的,这里使用:
<?xml version="1.0" encoding="utf-8"?> <local:Application xmlns:fx="
xmlns:local="*" width="500" height="100" click="tween()" > <fx:Script> <![CDATA[ import com.greensock.TweenLite; import com.greensock.easing.*; [Bindable] public var labelFontSize:Number = 20; private function tween():void { labelFontSize = 20; alpha = 1; TweenLite.to(this,3,{labelFontSize:0, alpha:0}); } ]]> </fx:Script> <local:Label id="label" x="{30}" y="30" text="雅美蝶!" defaultTextFormat="{new TextFormatter().setSize(labelFontSize)}" width="100" /> </local:Application>
由于使用了数据绑定和tweening,程序尺寸增到了27KB。下面是Flash程序,可用鼠标点击查看动画:
四、一个很重要的组件:BackGround
一旦能用图片作为背景,就可以实现出种种的皮肤效果,下面,引入一个非常重要的组件BackGround,支持嵌入图片,且具有九宫格。BackGround对带九宫格支持的 类进行了封装:
package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.DisplayObject; import flash.display.Graphics; import flash.display.Sprite; import flash.events.Event; import flash.geom.Matrix; import flash.geom.Rectangle;
public class BackGround extends BaseComponent { [Bindable] public var source:*; [Bindable] public var sourceScale9Grid:Rectangle; public function BackGround():void { super(); } public override function draw():void { super.draw(); if(source) { if(source is BitmapData) { drawBitmapData(source); } else if(source is Class) { var bmp:Bitmap = new source() as Bitmap; if(bmp != null) { drawBitmapData(bmp.bitmapData); } } } } private var _bgBitmap:DisplayObject = null; private function drawBitmapData(bmpData:BitmapData):void { if(_bgBitmap != null) { this.removeChild(_bgBitmap); _bgBitmap = null; } if(bmpData == null) return; var sb:ScaleBitmap = new ScaleBitmap(bmpData,"auto",true); sb.scale9Grid = this.sourceScale9Grid; sb.setSize(this.width,this.height); _bgBitmap = sb; this.addChild(sb); } } }
五、实现抽奖程序
现在万事俱备,让我们在上面介绍的基础上实现抽奖程序。直接使用吴秦的例子《》中的图片资源,把这些资源提取出来,分别命名为arrow.png、bg.jpg和top.png,放在src/assets目录下。
下面进行布局:
<?xml version="1.0" encoding="utf-8"?> <local:Application xmlns:fx=" xmlns:local="*" width="500" height="500" > <local:BackGround x="{width*0.5-0.5-0.5*428}" y="{height*0.5-0.5-0.5*427}" width="428" height="427" source="@Embed(source='assets/bg.jpg')" /> <local:BackGround x="{width*0.5-0.5-0.5*103}" y="{height*0.5-0.5-0.5*103}" width="103" height="103" source="@Embed(source='assets/top.png')" /> </local:Application>
效果图:
下面添加Arrow类:
package { import flash.display.Bitmap; import flash.display.Graphics;
public class Arrow extends BaseComponent { [Embed(source='assets/arrow.png')] public var arrowClass:Class; public var offset:Number = 60; // 箭头资源的末端离圆心的偏移量 private var _angle:Number = 0; public function get angle():Number { return _angle; } public function set angle(value:Number):void { _angle = value; this.rotation = value; } public override function draw():void { this.removeAllChildren(); var g:Graphics = this.graphics; g.clear(); var bmp:Bitmap = new arrowClass() as Bitmap; bmp.x = -bmp.width/2; bmp.y = -bmp.height/2 - offset; this.addChild(bmp); } } }
上类offset是箭头资源的末端离圆心的偏移量。而由于rotation 的取值范围为-180~180,不能直接用在动画中,因此,添加angle字段对它进行封装。
下面将Arrow类添加到布局中:
<?xml version="1.0" encoding="utf-8"?> <local:Application xmlns:fx=" xmlns:local="*" width="500" height="500" > <local:BackGround x="{width*0.5-0.5-0.5*428}" y="{height*0.5-0.5-0.5*427}" width="428" height="427" source="@Embed(source='assets/bg.jpg')" /> <local:Arrow x="{width*0.5-0.5}" y="{height*0.5-0.5}" > </local:Arrow> <local:BackGround x="{width*0.5-0.5-0.5*103}" y="{height*0.5-0.5-0.5*103}" width="103" height="103" source="@Embed(source='assets/top.png')" /> </local:Application>
效果图:
像模像样了吧!
万事俱备,下面添加动画,为了方便,这里用固定值,实际应用中,应该是去服务器取值:
<?xml version="1.0" encoding="utf-8"?> <local:Application xmlns:fx="
xmlns:local="*" width="500" height="500" > <fx:Script> <![CDATA[ import com.greensock.TweenLite; import com.greensock.easing.*; private function roll(val:int):void { val = val%8; arrow.angle = arrow.rotation; // 把 angle 恢复到 -180-180 之间 var newAngle:Number = val * 360 / 8 + 360 * 5; // 多转几圈 TweenLite.to(arrow, 10, { angle:newAngle, ease:Expo.easeOut }); // 使用 Expo.easeOut 让转动先快后慢 } ]]> </fx:Script> <local:BackGround x="{width*0.5-0.5-0.5*428}" y="{height*0.5-0.5-0.5*427}" width="428" height="427" source="@Embed(source='assets/bg.jpg')" /> <local:Arrow id="arrow" x="{width*0.5-0.5}" y="{height*0.5-0.5}" > </local:Arrow> <local:BackGround x="{width*0.5-0.5-0.5*103}" y="{height*0.5-0.5-0.5*103}" width="103" height="103" source="@Embed(source='assets/top.png')" click="roll(4)" buttonMode="true" useHandCursor="true" /> </local:Application>
下面看动画,点击中间的“抽奖”按钮即可进行抽奖:
程序大小:58KB。比吴秦的例子(68KB)中还要小10K。而如果不用数据绑定(仔细观看上面代码,这个例子中数据绑定无用),代码的尺寸还可以小上几K。 而编码量呢?抛除基础类,代码量只有 29 + 37 = 66 行(算进空格,{,}等)!
66行代码!!!代码就不打包下载了,如果感兴趣,自己动手做一遍会比直接拿Demo编译收获更大。