1.接口
interface wargrey.identification.IExtensible { /** * @description: public function extend(Object):Void; */ } |
IExtensive接口是一个标识接口,用于表示实现此接口的类是“可扩展的”。不过可能有一点比较奇怪,为什么其方法要注释掉?其实这也是因为AS2在面向对象方面的不足,因为AS2本身是一门动态语言,这类语言的特点之一便是类型弱化的,而这一特征比面向对象的方法重载更灵活,可以因为这个原因,AS一直不支持方法重载,而对于面向对象,又是强类型的,因此实现接口时必须严格实现匹配的接口方法。本来这没有矛盾,可以保留public function extend(Object):Void;方法,但很明显本例的扩展性是针对可视对象的,因此参数Object是比较宽的类型,为了避免不要必要的麻烦,因此设计成让开发者自我约束的方式:与其让开发者关注被扩展的对象类型,还不如原本就不知道有更多的方法可选,况且这样更利于定制系统所需要的接口。这是针对动态语言的解决方案,不适合带到其他静态面向对象语言。例外本例并非没有其他更规范的解决方案,只是这样的设计体现的一种思想,因为本人认为,软件设计在很多时候都需要反规范。本例实现此接口的类实现了public function extend(MovieClip):Void;方法。
2.实用工具类
a.网格系统Grids:
class wargrey.util.Grids { private var _currentGrid:Object; //当前网格副本 public function get currentGrid():Object{ return this._currentGrid; } public function set currentGrid(pos:Object):Void{ var x=(pos.x==undefined)?pos._x:pos.x; var y=(pos.y==undefined)?pos._y:pos.y; if ((x!=undefined)&&(y!=undefined)){ var w=(pos.width==undefined)?pos._width:pos.width; var h=(pos.height==undefined)?pos._height:pos.height; if (w!=undefined)x=x+w; if (h!=undefined)y=y+h; this._currentGrid.x=Math.floor(x/this._width)*this._width+1; this._currentGrid.y=Math.floor(y/this._height)*this._height+1; } } private var _width:Number = 25; //网格宽 public function get width():Number{ return this._width; } private var _height:Number = 25; //网格高 public function get height():Number{ return this._height; }
public function Grids(w:Number,h:Number){ if((w!=undefined)&&(w>=1))this._width=Math.round(w); if((h!=undefined)&&(h>=1))this._height=Math.round(h); this._currentGrid=new Object(); this._currentGrid.x=1; this._currentGrid.y=1; this._currentGrid.width=this._width; this._currentGrid.height=this._height; } public function getCurrentGrid(pos:Object):Object{ if (pos!=undefined)currentGrid=pos; return this._currentGrid; } public function getNextRowGrid(pos:Object):Object{ if (pos!=undefined)currentGrid=pos; this._currentGrid.x+=this._width; return this._currentGrid; } public function getNextLineGrid(pos:Object):Object{ if (pos!=undefined)currentGrid=pos; this._currentGrid.y+=this._height; this._currentGrid.x=1; return this._currentGrid; } } |
网格系定义了网格的一系列属性,作用是为任一MovieClip划分行和列,以方便该MovieClip上其他MovieClip的位置布局,并且不受该MovieClip上可能存在的坐标系统的影响。
网格系统共三个属性,分别为:
width(Number):只读属性网格宽。初始大小在构造网格系时传递,默认为25,单位像素。
height(Number):只读属性网格高。初始大小在构造网格系时传递,默认为25,单位像素。
currentGrid(Object):当前网格位置。Object是一个包含位置属性的对像,无所谓其是否可视,实际上若该对象可视,会忽略其的这一特性。位置被定义为Object在MovieClip上所处的网格的左上顶点的MovieClip坐标。当currentGrid=Object发生时,object的大小或位置不会正好匹配网格大小或顶点,因此传入参数将被视为该Object右下顶点。
网格系还有三个方法getCurrentGrid(Object),getNextRowGrid(Object)和getNextLineGrid(Object),这三个方法的作用依次是获取相对Object的当前网格、下一列网格和下一行网格对象,并且在该方法发生后,currentGrid都会被设置成相对Object的相应网格。关于Object的说明同上。
b.坐标系统Coordinates:
class wargrey.util.Coordinates{ private var coordinateX:Number; //坐标系X轴原点所在的影片坐标系坐标 private var coordinateY:Number; //坐标系Y轴原点所在的影片坐标系坐标 private var scaleX:Number; //坐标系X轴缩放比,值为 影片尺寸:实际尺寸 private var scaleY:Number; //坐标系Y轴缩放比,值为 影片尺寸:实际尺寸 private var _scale:Number; //坐标系缩放比,值由scaleX和scaleY决定 public function get scale():Number{ return _scale; }
public function Coordinates(coordinateX:Number,coordinateY:Number,scaleX:Number,scaleY:Number){ this.coordinateX=(coordinateX==undefined)?0:coordinateX; this.coordinateY=(coordinateY==undefined)?0:coordinateY; this.scaleX=((scaleX!=undefined)&&(scaleX>=0))?scaleX:1; this.scaleY=((scaleY!=undefined)&&(scaleY>=0))?scaleY:1; var radian=Math.atan2(this.scaleY,this.scaleX); this._scale=scaleY/Math.sin(radian); } public function mapping(target:Object):Object{ if (target instanceof Number){ return target*_scale; }else{ var x:Number=(target.x==undefined)?target._x:target.x; var y:Number=(target.y==undefined)?target._y:target.y; var w:Number=(target.width==undefined)?target._width:target.width; var h:Number=(target.height==undefined)?target._height:target.height; var result:Object=new Object(); if(x!=undefined)result.x=coordinateX+x*scaleX; if(y!=undefined)result.y=coordinateY-y*scaleY; if(w!=undefined)result.width=w*scaleX; if(h!=undefined)result.height=h*scaleY; return result; } } public function converse(target:Object):Object{ if (target instanceof Number){ return target/_scale; }else{ var x:Number=(target.x==undefined)?target._x:target.x; var y:Number=(target.y==undefined)?target._y:target.y; var w:Number=(target.width==undefined)?target._width:target.width; var h:Number=(target.height==undefined)?target._height:target.height; var result:Object=new Object(); if(x!=undefined)result.x=(x-coordinateX)/scaleX; if(y!=undefined)result.y=-(y-coordinateY)/scaleY; if(w!=undefined)result.width=w/scaleX; if(h!=undefined)result.height=h/scaleY; return result; } } } |
坐标系统使得开发人员可以为MovieClip添加自定义坐标系的能力,这样一来对矢量的控制将更为方便。与网格系相比,坐标系更加强调与MovieClip的绑定。
坐标系只有一个只读属性scale(Number),表示坐标缩放比(影片尺寸:实际尺寸,以对角线为准)。其大小由构造时传入的scaleX和scaleY决定,默认为21/2。
坐标系有两个核心方法mapping(Object)和converse(Object)。Object是一个具有位置和大小属性的对象,同样也无所谓是否可视。这两个方法分别用于“将实际对象按其位置和大小映射为MovieClip对象”及“MovieClip对象按其位置和大小还原为实际对象”。
补充说明:
-
坐标系的(0,0)点对应于MovieClip坐标上的一个点,该点再构造时传入,默认为(0,0)。
-
一旦为MovieClip绑定了坐标系,则无论是否指定坐标参数,MovieClip都具有了一个正常的数学直角坐标系。
-
mapping和converse两个方法的参数也可以就是Number,这样就根据scale值只作长度变换。不过转化后的值受scaleX和scaleY的夹角的影响。
3.核心类
import mx.core.UIObject; import wargrey.util.*; import wargrey.identification.IExtensible; class wargrey.extension.GraphicalInterface extends UIObject implements IExtensible{ private var grids:Grids; //网格系统 private var coordinates:Coordinates; //坐标系统
private var currentX:Number; //光标坐标x private var currentY:Number; //光标坐标y private var currentZ:Number; //光标坐标z,即影片深度 private var textFields:Number; //用于构建display生成的文本域名称序号
public function GraphicalInterface(cs:Coordinates,gs:Grids){ setGraphicals(cs,gs); var loc=coordinates.converse({x:0,y:0}); currentX=loc.x; currentY=loc.y; currentZ=1; textFields=1; } public function setGraphicals(cs:Coordinates,gs:Grids):Void{ if (cs!=undefined)coordinates=cs; if (coordinates==undefined)coordinates=new Coordinates(); if (gs!=undefined)grids=gs; if (grids==undefined)grids=new Grids(); } public function createVisualObject(target:Object, id:Object, init:Object):MovieClip{ var von=(id==undefined)?"viusalobjectatdepth"+currentZ.toString():id.toString(); if (target instanceof Function){ var obj:UIObject=this.createClassObject(Function(target),von,getNextDepth(),init); }else{ var obj:MovieClip=this.attachMovie(target.toString(),von,getNextDepth(),init); } var rect:Object=new Object(); rect.x=(init.x==undefined)?init._x:init.x; rect.y=(init.y==undefined)?init._y:init.y; if (rect.x==undefined)rect.x=currentX; if (rect.y==undefined)rect.y=currentY; rect.width=(init.width==undefined)?init._width:init.width; rect.height=(init.height==undefined)?init._height:init.height; if (rect.width==undefined)rect.width=obj._width; if (rect.height==undefined)rect.height=obj._height; var w=rect.width; var h=rect.height; rect=coordinates.mapping(rect); obj._x=rect.x; obj._y=rect.y; if (!init.scale){ rect.width=w; rect.height=h; } obj.width=rect.width; obj.height=rect.height; newRow(obj); return obj; } public function display(msg:Object, size:Number, textColor:Number, backColor:Number, text:TextField, format:TextFormat):TextField{ if (!((size==undefined)&&(textColor==undefined)&&(format==undefined))){ var ft:TextFormat=(format==undefined)?new TextFormat():format; if (size!=undefined)ft.size=size; if (textColor!=undefined)ft.color=textColor; } var tfn:String="display"+(textFields++).toString(); var pos=coordinates.mapping({x:currentX,y:currentY}); createTextField(tfn,getNextDepth(),pos.x,pos.y,0,0); var temp:TextField=TextField(this[tfn]); if (text!=undefined)temp=text; if (backColor!=undefined){ temp.background=true; temp.backgroundColor=backColor; } temp.type="dynamic"; if (temp.antiAliasType!="normal")temp.antiAliasType="advanced"; temp.autoSize=true; temp.html=true; temp.htmlText=msg.toString(); if (ft!=undefined)temp.setTextFormat(ft); newRow(temp); return temp; } public function newLine(obj:Object):Void{ var cr=coordinates.converse(grids.getNextLineGrid(obj)); currentX=cr.x; currentY=cr.y; } public function newRow(obj:Object):Void{ var cr=coordinates.converse(grids.getNextRowGrid(obj)); currentX=cr.x; } public function locate(x:Number,y:Number):Void{ currentX=x; currentY=y; var p:Object=new Object(); p.x=x; p.y=y; p=coordinates.mapping(p); grids.currentGrid=p; } public function horizontalAlign(ui:Array,align:String):Void{ align=align.toLowerCase(); var min=ui[0]._x; var max=ui[0]._x+ui[0]._width; for (var i=1;i<ui.length;i++){ var l=ui[i]._x; var r=ui[i]._width+l; if (min>l)min=l; if (max<r)max=r; } if (align=="left"){ for(var i=0;i<ui.length;i++)ui[i]._x=min; }else if(align=="right"){ for(var i=0;i<ui.length;i++)ui[i]._x=max-ui[i]._width; }else if ((align=="centre")||(align=="center")||(align=="middle")){ for(var i=0;i<ui.length;i++)ui[i]._x=Math.round((min+max-ui[i]._width)/2); } } public function verticalAlign(ui:Array,align:String):Void{ align=align.toLowerCase(); var min=ui[0]._y; var max=ui[0]._y+ui[0]._height; for (var i=1;i<ui.length;i++){ var t=ui[i]._y; var b=ui[i]._height+t; if (min>t)min=t; if (max<b)max=b; } if (align=="top"){ for(var i=0;i<ui.length;i++)ui[i]._y=min; }else if(align=="bottom"){ for(var i=0;i<ui.length;i++)ui[i]._y=max-ui[i]._height; }else if ((align=="centre")||(align=="center")||(align=="middle")){ for(var i=0;i<ui.length;i++)ui[i]._y=Math.round((min+max-ui[i]._height)/2); } } public function gridAlign(ui:Array):Void{ var cg=grids.currentGrid; for (var i=0;i<ui.length;i++){ var loc:Object=new Object(); loc.x=ui[i]._x; loc.y=ui[i]._y; loc=grids.getCurrentGrid(loc); ui[i]._y=loc.y; } grids.currentGrid=cg; }
public function extend(target:MovieClip):Void{ var loc=coordinates.converse({x:0,y:0}); target["coordinates"]=this.coordinates; target["grids"]=this.grids; target["currentX"]=loc.x; target["currentY"]=loc.y; target["currentZ"]=1; target["textFields"]=1;
target["setGraphicals"]=this.setGraphicals; target["createVisualObject"]=this.createVisualObject; target["display"]=this.display; target["newLine"]=this.newLine; target["newRow"]=this.newRow; target["locate"]=this.locate; target["horizontalAlign"]=this.horizontalAlign; target["verticalAlign"]=this.verticalAlign; target["verticalAlign"]=this.verticalAlign; target["gridAlign"]=this.gridAlign; target["getNextDepth"]=this.getNextDepth; target["destroyObject"]=this.destroyObject;
if (target instanceof IExtensible){ var oldExtend:Function=target.extend; var thisExtend:Function=this.extend; var newExtend:Function=function(target:Object){ oldExtend(target); thisExtend(target); }; target.extend=newExtend; } }
private function getNextDepth():Number{ if (currentZ<1048574)currentZ++; var depth=currentZ; return depth; } } |
GraphicalInterface类实现了IExtensible接口,并借助网格系和坐标系来扩展MovieClip实例。所谓的扩展其实跟混入用了同样的手法,下面详细介绍:
setGraphicals(Coordinate,Grids):指定被扩展对象所绑定的坐标系和网格系,省略则创建默认实例。构造函数的参数与此相同。
createVisualObject(target:Object,id:Object,init:Object):MovieClip:创建一个有init初始化的可视对象target,target可以是一个UIObject类或库中的MovieClip实例,其名称由id.toString()决定。id和init可选,若id省略,则target的名称由影片深度决定;若init省略,则在当前位置创建默认实例,init有个隐含属性scale,指示对象的大小是否也被映射,init的位置和大小属性都针对实际坐标而言。返回被创建的对象。
display(msg:Object,sz:Number,tc:Number,bc:Number,tt:TextField,tf:TextFormat):TextField:将tf格式化后的msg.toString()显示在tt上,或者也可以简单一点省略tt和tf,msg的大小有sz决定,前景和背景色分别有tc和bc决定。如果省略tt,将在当前位置新建一个TextField对象。返回显示了msg的TextField对象,该对象支持html。
locate(Number,Number),newRow(Object),newLine(Object):这三个方法用于定位。有关参数Object的说明见Grids。locate方法的参数是实际坐标。
horizontalAlign(Array,String),verticalAlign(Array,String),gridAlign(Array):这三个方法用于将Array里的可视对象分别进行水平对齐,竖直对齐和网格对齐。对于水平和竖直对齐,效果与Flash的align工具相同,String的取值有Top(Left)、Bottom(Right)、Center三种情况。
extend(MovieClip):这是扩展MovieClip的方法,从这里我们看到了动态语言的优势。因此建议被扩展的对象是被声明为dynamic的,不然混入的方法就只能通过数组访问操作符来引用了。
4.测试用例
下面给出一个完整的测试用例来演示该框架。
// TestArea.as import mx.controls.Button; import wargrey.extension.GraphicalInterface; class wargreytest.extension.graphicalinterface.TestArea extends MovieClip { private var c:Button; private var ha:Button; private var va:Button; private var hua:Array; private var vua:Array;
public function set creatable(b:Boolean):Void{ c.enabled=b; } public function set VAable(b:Boolean):Void{ va.enabled=b; } public function set HAable(b:Boolean):Void{ ha.enabled=b; }
private function click(evt:Object):Void{ switch(evt.target){ case ha: var t=Math.round(Math.random()*4+1); var type:String; if (t==1){ type="Left"; ha.label="左端对齐"; }else if (t==2){ type="Right"; ha.label="右端对齐"; }else if (t==3){ type="Center"; ha.label="居中对齐"; } if (t==4){ this["gridAlign"](hua); ha.label="网格对齐"; }else{ this["horizontalAlign"](hua,type); } break; case va: var t=Math.round(Math.random()*4+1); var type:String; if (t==1){ type="Top"; va.label="顶端对齐"; }else if (t==2){ type="Bottom"; va.label="底端对齐"; }else if (t==3){ type="Center"; va.label="居中对齐"; } if (t==4){ this["gridAlign"](vua); va.label="网格对齐"; }else{ this["verticalAlign"](vua,type); } break; case c: ha.label="水平对齐"; va.label="竖直对齐"; if (this["h1"]!=undefined){ //this["destroyObject"]("h1"); //this["destroyObject"]("h2"); //this["destroyObject"]("h3"); //this["destroyObject"]("v1"); //this["destroyObject"]("v2"); //this["destroyObject"]("v3"); //for (var i=1;i<this["textFields"];i++)this["display"+i.toString()].removeTextField(); var loc=this["coordinates"].converse({x:0,y:0}); this["currentX"]=loc.x; this["currentY"]=loc.y; this["currentZ"]=1; } this["display"]("下列组件用于演示水平对齐",20,0x00ff00,0x0000ff); for(var i=1;i<=3;i++){ var p:Object=new Object(); p.x=Math.round(Math.random()*100+1); p.y=i*30; p=this["coordinates"].converse(p); var w=Math.round(Math.random()*100+100); this["locate"](p.x,p.y); this["h"+i.toString()]=this["createVisualObject"](Button,"h"+i.toString(),{_width:w}); this["h"+i.toString()].label="HATest"+i.toString(); hua[i-1]=this["h"+i.toString()]; } this["newLine"](); this["display"]("下列元件用于演示竖直对齐",20,0x0000ff,0x00ff00); for(var i=1;i<=3;i++){ var x=[5,100,200]; var p:Object=new Object(); p.x=x[i-1]; p.y=Math.round(Math.random()*50+150); var w=Math.round(Math.random()*40+50); p=this["coordinates"].converse(p); this["locate"](p.x,p.y); this["v"+i.toString()]=this["createVisualObject"]("VA"+i.toString(),"v"+i.toString(),{_width:w}); vua[i-1]=this["v"+i.toString()]; } VAable=true; HAable=true; break; } } private function onLoad():Void{ hua=new Array(3); vua=new Array(3); c.addEventListener("click",this); ha.addEventListener("click",this); va.addEventListener("click",this); } } // GraphicalInterfaceTest.as import mx.controls.Label; import mx.controls.TextInput; import mx.controls.Button; import wargreytest.extension.graphicalinterface.TestArea; import wargrey.extension.GraphicalInterface; import wargrey.util.Coordinates; import wargrey.util.Grids; class wargreytest.extension.GraphicalInterfaceTest extends MovieClip { private var lblmc:Label; private var lblac:Label; private var lblop:Label; private var lbldh:Label; private var lblkh:Label; private var lblwg:Label; private var lblh:Label; private var lbls:Label; private var lbly:Label; private var txtmc:Label; private var txtac:Label; private var txtgc:Label; private var x:TextInput; private var y:TextInput; private var sx:TextInput; private var sy:TextInput; private var width:TextInput; private var height:TextInput; private var area:TestArea; private var initialize:Button;
function GraphicalInterfaceTest() { super(); }
private function onLoad():Void{ initialize.addEventListener("click",this); initialize.label="Begin"; x.text="150"; y.text="150"; sx.text="0.5"; sy.text="0.5"; width.text="12"; height.text="24"; area.creatable=false; area.VAable=false; area.HAable=false; }
private function click():Void{ initialize.label="Reset"; var c:Coordinates=new Coordinates(Number(x.text),Number(y.text),Number(sx.text),Number(sy.text)); var g:Grids=new Grids(Number(width.text),Number(height.text)); var gi=new GraphicalInterface(c,g); gi.extend(area); area.creatable=true; var self=this; var mouse:Object=new Object(); mouse.onMouseMove=function(){ if ((self._xmouse>self.area._x)&&(self._xmouse<(self.area._x+self.area._width))&&(self._ymouse>self.area._y)&&(self._ymouse<(self.area._y+self.area._height))){ var p:Object=new Object(); p.x=self.area._xmouse; p.y=self.area._ymouse; self.txtmc.text="("+Math.round(p.x)+","+Math.round(p.y)+")"; p=self.area["coordinates"].converse(p); self.txtac.text="("+Math.round(p.x)+","+Math.round(p.y)+")"; p=self.area["grids"].getCurrentGrid(self.area["coordinates"].mapping(p)); self.txtgc.text="("+Math.round(p.x)+","+Math.round(p.y)+")"; } }; Mouse.addListener(mouse); } } |