在底图调用完成后,我们要为地图添加一些工具,首先就是出场率很高的鹰眼图。我这里的鹰眼图是从ESRI的Flex Viewer中剥离出来的,申明一下,怕ESRI说我侵权。经常有人质疑我为什么不直接用Viewer而要费心思自己去写一些组件。我个人的经验是Viewer东西太多了,很臃肿,对于我们这样有整洁强迫症的人来说工程里存在大量用不着的代码非常难受,其实也不光是心理作用了,确实Viewer不经过大量优化会加载比较慢。但是,Viewer中有很多思想和很多组件是非常好用的,比如这个鹰眼,所以我们依然要借鉴它们。先看效果:
废话多了一点,下面开始正题。首先我们看一下鹰眼的大致布局。主体是一个SkinnableContainer,一个BorderContainer作为主容器,里面放下map和展开按钮,viewer为了美观还放下两个Rect作为边框,map里面设置的属性有点儿多,主要是鹰眼图是不接受鼠标缩放平移这些操作的,鼠标在鹰眼里只能控制主map范围框。
为了控制收放状态,还设置了collapsed和expanded两个状态,布局里也做一些相应的设置,下面看一下SkinnableContainer内的布局。
<s:states> <s:State name="collapsed"/> <s:State name="expanded"/> </s:states> <s:BorderContainer id="viewBox" width.collapsed="{toggleIcon.width}" width.expanded="250" height.collapsed="{toggleIcon.height}" height.expanded="250" backgroundAlpha="1.0"> <esri:Map id="overviewMap" width="250" height="250" clickRecenterEnabled="false" doubleClickZoomEnabled="false" keyboardNavigationEnabled="false" load="overviewMap_loadHandler(event)" logoVisible="false" mask="{mapMask}" panArrowsVisible="false" panEnabled="false" rubberbandZoomEnabled="false" scaleBarVisible="false" scrollWheelZoomEnabled="false" wrapAround180="{map.wrapAround180}" zoomSliderVisible="false"> <tool:TDTTiledMapLayer id="vec_c" baseUrl="http://t0.tianditu.com/vec_c/wmts" layerId="vec" /> <tool:TDTTiledMapLayer id="cva_c" baseUrl="http://t0.tianditu.com/cva_c/wmts" layerId="cva" /> </esri:Map> <s:BorderContainer id="mapMask" width="{viewBox.width}" height="{viewBox.height}"/> <s:Group width="{viewBox.width}" height="{viewBox.height}"> <mx:Image id="toggleIcon" width="23" height="23" buttonMode="true" mask="{iconMask}" smoothBitmapContent="true" source="image/i_expand2.png" toolTip.collapsed="打开鹰眼图" toolTip.expanded="关闭鹰眼图" useHandCursor="true"/> <s:BorderContainer id="iconMask" width="{toggleIcon.width}" height="{toggleIcon.height}"/> </s:Group> </s:BorderContainer> <s:Rect left="-1" right="-1" top="-1" bottom="-1"> <s:stroke> <s:SolidColorStroke color="#000000" weight="1"/> </s:stroke> </s:Rect> <s:Rect left="-5" right="-5" top="-5" bottom="-5"> <s:stroke> <s:SolidColorStroke color="#000000" weight="2"/> </s:stroke> </s:Rect>
下面是核心内容,要解决两个关键问题,一个是overviewmap和主map的协同,一个是overviewmap中显示主map的范围框,并能拖拽范围框控制主map。两个问题的很多代码要在同一事件中处理,就不分开说了,一起来看,主要内容我写在代码的注释里。
这里要提出一个重要的东西,那就是:所有的地图工具组件都有要传入主map作为参数!这一点会在后面的一些工具中也出现。现在我们就定义鹰眼图的一些变量,先是map,再定义一下范围框所需的GraphicLayer、Graphic和Symbol,还要定义一些控制范围框所需的变量。
//范围框的symbol private const lineSym:SimpleLineSymbol = new SimpleLineSymbol("solid", 0xFF0000, 0.7, 2); private const currentExtentSym:SimpleFillSymbol = new SimpleFillSymbol("solid", 0xFF0000, 0.2, lineSym); //传入的map参数 [Bindable] public var map:Map; //用于显示范围框的图层 private var graphicsLayer:GraphicsLayer = new GraphicsLayer(); //范围框的Graphic private var overviewGraphic:Graphic = new Graphic(); //标识范围框是否被移动 private var hasOverviewGraphicBeenMoved:Boolean = false; //范围框移动的偏移量 private var xOffset:Number; private var yOffset:Number; //鹰眼图按钮的ToolTip [Bindable] private var openToolTip:String; [Bindable] private var closeToolTip:String;
接着就是初始化,这里的初始化是分别在组件和map两个层面进行的,主要是为了防止子组件初始化生命周期不同步带来BUG。一是给SkinnableContainer加上Init事件,初始化一些东西,其中toggleIcon的click事件后面会专门提,先略过;二是在overviewMap加载图层完毕后,初始化范围框和协同所需的一些事件。
private function init():void { overviewGraphic.buttonMode = true; toggleIcon.addEventListener(MouseEvent.CLICK, toggleIcon_clickHandler); //默认收起就把图层隐藏,减少资源占用 if (currentState == "collapsed") { for each (var layer:Layer in overviewMap.layers) { layer.visible = false; } } } //鹰眼图所需的图层加载完毕后的一些初始化,渲染范围框、绑定范围框的拖拽事件之类 private function overviewMap_loadHandler(event:MapEvent):void { graphicsLayer.name = "overviewMapGraphicsLayer"; graphicsLayer.symbol = currentExtentSym; overviewMap.addLayer(graphicsLayer); overviewGraphic.geometry = map.extent; //初始只需要一个鼠标按下 overviewGraphic.addEventListener(MouseEvent.MOUSE_DOWN, overviewGraphic_mouseDownHandler); //鼠标移出鹰眼的处理 overviewMap.addEventListener(MouseEvent.ROLL_OUT, overviewMap_mouseRollOutHandler); graphicsLayer.add(overviewGraphic); map.addEventListener(ExtentEvent.EXTENT_CHANGE, map_extentChangeHandler); updateOverviewExtentFromMap(); }
overviewMap初始化绑定了一系列事件,这些事件就是用了解决overviewMap和主map之间的那点事儿,先看主map变化联动overviewMap部分,写在map_extentChangeHandler中:
private function map_extentChangeHandler(event:ExtentEvent):void { //主map的extent变化时刷新鹰眼中的范围框 updateOverviewExtentFromMap(); //地图范围框超出鹰眼范围时隐藏overviewGraphic if (!overviewMap.extent.contains(overviewGraphic.geometry)) { overviewGraphic.visible = false; } else { overviewGraphic.visible = true; } } private function updateOverviewExtentFromMap():void { //鹰眼图的extent是主map的3倍 overviewMap.extent = map.extent.expand(3); //把主map的extent作为范围框 overviewGraphic.geometry = map.extent; }
再看鹰眼范围框的拖拽控制主map范围变化:
//对范围框按下鼠标以后移除mousedown,绑定mouseup,记录下初始的点击点和范围框中心点的相对关系 private function overviewGraphic_mouseDownHandler(event:MouseEvent):void { overviewGraphic.removeEventListener(MouseEvent.MOUSE_DOWN, overviewGraphic_mouseDownHandler); overviewMap.addEventListener(MouseEvent.MOUSE_UP, overviewMap_mouseUpHandler); overviewMap.addEventListener(MouseEvent.MOUSE_MOVE, overviewMap_mouseMoveHandler); var overviewCenterMapPoint:MapPoint = overviewGraphic.geometry.extent.center; var mouseMapPoint:MapPoint = overviewMap.toMapFromStage(event.stageX, event.stageY); xOffset = mouseMapPoint.x - overviewCenterMapPoint.x; yOffset = mouseMapPoint.y - overviewCenterMapPoint.y; } //move事件控制范围框的拖拽,根据相对关系恢复范围框的瞬时位置 private function overviewMap_mouseMoveHandler(event:MouseEvent):void { hasOverviewGraphicBeenMoved = true; var overviewExtent:Extent = overviewGraphic.geometry as Extent; var mouseMapPoint:MapPoint = overviewMap.toMapFromStage(event.stageX, event.stageY); mouseMapPoint.x -= xOffset; mouseMapPoint.y -= yOffset; overviewGraphic.geometry = overviewExtent.centerAt(mouseMapPoint); } //鼠标松开,移除move和up时间,记录下结束位置,同时刷新主map private function overviewMap_mouseUpHandler(event:MouseEvent):void { overviewMap.removeEventListener(MouseEvent.MOUSE_MOVE, overviewMap_mouseMoveHandler); overviewMap.removeEventListener(MouseEvent.MOUSE_UP, overviewMap_mouseUpHandler); overviewGraphic.addEventListener(MouseEvent.MOUSE_DOWN, overviewGraphic_mouseDownHandler); if (hasOverviewGraphicBeenMoved) { hasOverviewGraphicBeenMoved = false; updateMapExtentFromOverview(); } } //鼠标移出了鹰眼的处理,移除move和mouseup事件,刷新主map范围,这对于用户体验很重要 private function overviewMap_mouseRollOutHandler(event:MouseEvent):void { overviewMap.removeEventListener(MouseEvent.MOUSE_MOVE, overviewMap_mouseMoveHandler); overviewMap.removeEventListener(MouseEvent.MOUSE_UP, overviewMap_mouseUpHandler); overviewGraphic.addEventListener(MouseEvent.MOUSE_DOWN, overviewGraphic_mouseDownHandler); if (hasOverviewGraphicBeenMoved) { hasOverviewGraphicBeenMoved = false; updateMapExtentFromOverview(); } } private function updateMapExtentFromOverview():void { map.extent = overviewGraphic.geometry as Extent; }
这样鹰眼图就基本完成了,最后为了用户的体验,我们给展开按钮加上一个transition动画,在动画的基础上来控制收放,控制收放的关键其实都写在mxml布局里,就是那些width和height的绑定啦。又是绑定,很重要吧,很潇洒吧~~~
<!--这是展开按钮点击后的一个动画效果--> <s:transitions> <s:Transition fromState="*" toState="*"> <s:Sequence effectEnd="sequence_effectEndHandler(event)" effectStart="sequence_effectStartHandler(event)"> <s:Resize duration="800" target="{viewBox}"/> <s:Rotate angleBy="180" autoCenterTransform="true" duration="300" target="{toggleIcon}"/> </s:Sequence> </s:Transition> </s:transitions> private function sequence_effectStartHandler(event:EffectEvent):void { if (currentState == "expanded") { for each (var layer1:Layer in overviewMap.layers) { layer1.visible = true; } } } private function sequence_effectEndHandler(event:EffectEvent):void { if (currentState == "collapsed") { for each (var layer:Layer in overviewMap.layers) { if (!(layer is GraphicsLayer && layer.name == "overviewMapGraphicsLayer")) { layer.visible = false; } } } }
如此就完成了,在主页面里调用一下这个组件:
<component:OverviewMap right="3" bottom="3" map="{map}"> </component:OverviewMap>
稍微总结一下,鹰眼图的核心问题就是要在让主map和overviewMap协同起来,主map动overviewMap也动,overviewMap中移动范围框主map移动至相应的范围。实现的关键就是主map的extent事件和范围框嵌套的鼠标down、move、up事件。