地图级别控制器俗称“鱼骨头”,这几乎是Web地图的标配了。ESRI的flex api自带了级别控制器叫做zoomSlider,我觉得是非常的难看,难看的和“鱼骨头”都不太沾边,给任何客户提供这样的一个组件都是非常不严肃的事情,我的选择是要么索性不提供这个工具,要么,就自己做一个。这是我做的组件的效果:
这个组件可以整合很多的地图工具,本节只讨论中间的那个滑块部分,就是从“+”到“-”之间的部分,其他的工具留给下一节。
先来看布局,一个Group把所有的部件框起来,放下两个按钮、两个Rect、两个BorderContainer,还有四个path。两个按钮很显然就是加减级别;一个BorderContainer作为级别条外框和刻度容器,两个个Rect分别是上部白色填充和下部蓝色填充;另一个BorderContainer作为滑块,滑块中放一个Rect既作为滑块的一个部分,又可作为级别刻度的模板;四个path就是右侧的级别标签。这些组件有一些相互压盖关系,顺序不要弄错。
<!--mouseOver和mouseOut是用于控制右侧级别标签的显示,高度是动态的--> <s:Group id="level_Contain" top="80" width="100%" height="{blockHeight + 40}" horizontalCenter="0" mouseOver="mouseOverHandler(event)" mouseOut="mouseOutHandler(event)"> <commonitem:NavButton id="btn_plus" top="0" horizontalCenter="0" icon="image/plus.png" click="btn_plus_clickHandler(event)"> </commonitem:NavButton> <!--上部填充Rect,只需填充为白色--> <s:Rect id="upRect" top="20" width="7" height="{block.y-20}" horizontalCenter="0"> <s:fill> <s:SolidColor color="#ffffff" /> </s:fill> </s:Rect> <!--下部填充Rect,只需填充为蓝色--> <s:Rect id="downRect" top="{block.y+1}" width="7" height="{blockHeight - block.y + 20}" horizontalCenter="0"> <s:fill> <s:SolidColor color="#269aea" /> </s:fill> </s:Rect> <!--作为级别条边框的BorderContainer,height加2让显示平滑--> <s:BorderContainer id="slider" top="19" width="9" height="{blockHeight + 2}" backgroundAlpha="0.0" borderColor="#535a61" borderWeight="1" horizontalCenter="0"> </s:BorderContainer> <!--滑块--> <s:BorderContainer id="block" y="{initBlock}" width="15" height="9" borderColor="#535a61" buttonMode="true" horizontalCenter="0" mouseDown="block_mouseDownHandler(event)"> <s:Rect id="level_block" width="5" height="1" horizontalCenter="0" verticalCenter="0"> <s:fill> <s:SolidColor color="#535a61" /> </s:fill> </s:Rect> </s:BorderContainer> <commonitem:NavButton id="btn_sub" top="{20+blockHeight}" horizontalCenter="0" icon="image/sub.png" click="btn_sub_clickHandler(event)"> </commonitem:NavButton> <!--街道,节约篇幅,市、省、国三级类似不放出了--> <s:Path horizontalCenter="20" y="{(mapLevels - countyLevel) * 8 + 15}" data="M 0 10 L 8 0 L 25 0 L 25 20 L 8 20 Z" visible="{isLabelShown}"> <s:fill> <s:SolidColor color="#269aea" /> </s:fill> </s:Path> <s:Label horizontalCenter="22" y="{(mapLevels - countyLevel) * 8 + 19}" color="#FFFFFF" fontWeight="bold" text="街" visible="{isLabelShown}"/> </s:Group>
布局里的按钮是我自己做的,也可以直接用button组件、图片来代替,不是重点。布局里很多部件的height、top、y都用了绑定值,这是为了适应map的LODS的需要。因为组件有大量的绑定值,所以需要定义比较多的变量,具体涵义见注释。重点说一下传入主map的方式,这个和前文鹰眼图有所不同,用了一个set属性。回顾一下,在鹰眼图中,为了防止组件生命周期不一致,地图和组件分开进行初始化。这里没有自己的map,没办法分成两个层面初始化,我就用了一个isCompleted来标识组件初始化完毕,这样组件就可以在传入map属性和组件初始化完成这两个节点上择机进行初始化。这样做主要是因为要动态添加级别刻度,这个是没办法用绑定的方式完成,必须使用Group的addElement()方法,若是传入map时,Group还没有进入自己的生命周期会带来灾难性后果。同时,这样做,如果map的级别发生了变化,组件还会重新刷新刻度。
//用set属性传入map,顺便做一些初始化 private var _map:Map; public function set map(value:Map):void { _map=value; _map.addEventListener(ExtentEvent.EXTENT_CHANGE,mapExtentChanged); if(isCompleted) init(); } public function get map():Map { return _map; } //允许label显示 public var labelVisible:Boolean = true; //控制label显示 [Bindable] private var isLabelShown:Boolean = false; //默认的*别 [Bindable] public var nationLevel:int = 4; //默认的省级别 [Bindable] public var provinceLevel:int = 7; //默认的市级别 [Bindable] public var cityLevel:int = 12; //默认的街道级别 [Bindable] public var countyLevel:int = 15; //初始滑块位置 [Bindable] private var initBlock:int; //初始级别条高度 [Bindable] private var blockHeight:int; //地图的总级别数 [Bindable] private var mapLevels:int; //初始的级别刻度top值 private var initTop:int = 25; //标识组件初始化状态 private var isCompleted:Boolean = false; //鼠标拖拽滑块时的初始点击位置 private var initY:Number; //鼠标拖拽滑块时的初始滑块位置 private var initBlockY:Number; //用isCompleted来避免组件生命周期不一致带来BUG private function creationCompleteHandler(event:FlexEvent):void { isCompleted = true; init(); } //初始化获取当前地图的级别数,滑块起始位置,级别条高度,以及放置刻度 private function init():void { if(_map) { mapLevels = _map.lods.length; initBlock = 21 + (mapLevels - map.level)*8; blockHeight = 5 + (mapLevels-1) * 8 + 5; slider.removeAllElements(); for(var delta:int = 1;delta<=mapLevels;delta++) { var block:Rect = new Rect(); block.width=5; block.height=1; block.horizontalCenter=0; block.fill = level_block.fill; block.top = initTop; slider.addElement(block); initTop += 8; } } }
然后就是map与滑块之间协同的一些事儿要处理,这个和鹰眼图有点类似,就是map的extentChange和滑块的mousedown,不同的是,因为这个组件比较小,用户拖拽滑块很容易跑出组件范围,所以要在mousedown以后把move和up事件绑定到全局,这样操作就非常平滑了。
//点击滑块条启用拖拽,注意要对整个this.parent绑定move和mouseup,这是因为组件本身有点小,鼠标很可能会在拖拽时移出组件,确保鼠标在任何位置都不会中断操作 private function block_mouseDownHandler(event:MouseEvent):void { initY = event.stageY; initBlockY = block.y; this.parent.addEventListener(MouseEvent.MOUSE_MOVE,block_mouseMoveHandler); this.parent.addEventListener(MouseEvent.MOUSE_UP,block_mouseUpHandler); } //滑块只在y方向变化,所以只考虑y值,而且滑块不能移出最大和最小刻度 private function block_mouseMoveHandler(event:MouseEvent):void { var deltaY:Number = event.stageY - initY; var buttonY:Number = initBlockY + deltaY; block.y = buttonY; if(buttonY < 21) block.y = 21; else if(buttonY > 11+blockHeight) block.y = 11+blockHeight; else block.y = buttonY; } //松开鼠标滑块必须落在某个刻度的位置上,地图也要相应变化 private function block_mouseUpHandler(event:MouseEvent):void { //level = 12 - Math.round((btn_Slider.y - 26)/10); block.y = (Math.round((block.y - 21)/8)) * 8 + 21; map.level = mapLevels - Math.round((block.y - 21)/8)-1; this.parent.removeEventListener(MouseEvent.MOUSE_MOVE,block_mouseMoveHandler); this.parent.removeEventListener(MouseEvent.MOUSE_UP,block_mouseUpHandler); } //图动滑块也要动,这个事件让“+”、“-”按钮也变得很容易实现 private function mapExtentChanged(event:ExtentEvent):void { block.y = (mapLevels - map.level - 1) * 8 + 21; }
然后就是把“+”和“-”两个按钮的控制加上,有了前面的工作,这个很容易。
private function btn_plus_clickHandler(event:MouseEvent):void { map.level+=1; } private function btn_sub_clickHandler(event:MouseEvent):void { map.level-=1; }
处理一下级别标签。我这里做的比较简单,就是可以通过属性设置是否需要这个标签,如果需要的话就把国、省、市、街的级别设置一下,鼠标移入就可以出现在指定级别刻度旁,移出就看不见。
//这个就是控制四个级别标签在鼠标移入时显示,移出时消失 private function mouseOverHandler(event:MouseEvent):void { if(labelVisible) isLabelShown = true; } private function mouseOutHandler(event:MouseEvent):void { if(labelVisible) isLabelShown = false; }
最后是在主map的页面里面调用它:
<component:MapNavigator left="10" top="10" map="{map}" labelVisible="true" nationLevel="4" provinceLevel="7" cityLevel="12" countyLevel="15"> </component:MapNavigator>
到此,这个“鱼骨头”就完成了,还有平移、放大、缩小、测距几个工具留给下文。惯例总结一下。这个“鱼骨头”的核心问题是要让滑块和主map的级别变化协同,使用的思想和鹰眼图十分类似。具体的程序中,需要通过一些绑定的变量来控制级别条、上下部填充、滑块的高度、位置等,这些都要能适应当前地图,并且动态变化。