ArcGIS FlexViewer
1、?? ArcGIS API for Flex-Widget开发部署流程 开发Widget开发工具:Flex Builder 1)开发环境配置: a)首先确保FlexBuilder(Flex SDK 4.6)安装成功,并下载Flex viewer(libs目录中包含ArcGIS API for Flexv1.0)。 至此,通过代码就完成了个基本的Widget UI 设计,已经有了最大化,最小化,关闭等功能。下一步,就是实现业务逻辑。 4) 访问地图 (Accessing a Map) 5) 在地图上显示业务数据
status: String 类型,在控制栏上显示的状态文本。 2、???FlexViewer源码包结构 Flex Viewer源代码是Flash Builder中标准的Flex工程,可直接导入到Flash Builder。下面分别介绍一下源代码包结构中的各个部分,包结构如下图所示。 1) src根目录:包含index.mxml、defaults.css和config.xml。index.mxml是系统入口点,也就是创建Flex Application实例的地方;defaults.css是Flex Viewer中用来定义组件样式的文件,所有对组件样式进行定义的css脚本都可以放到这个文件中,defaults.css在index.mxml中被引用;config.xml是缺省配置文件。 2) apps:Flex Viewer的良好设计使其具有很高的可配置性,包括底图、业务图层、各种服务器端资源、甚至是用户体验都是可配置的。apps包中的内容是不同应用系统的配置文件及各种所需资源。Flex Viewer可以在url参数中设置所需加载的配置文件,比如:在Flex Viewer的url后加上配置文件的信息“?config=apps/zh_CN/config.xml”,Flex Viewer就会加载apps/zh_CN下的config.xml文件,根据该配置文件中的信息来配置整个Flex Viewer系统。也就是说,Flex Viewer根据不同的配置文件可以展现完全不同的应用系统。 3) assets.images:Flex Viewer使用的各种图片、flash资源所在的目录。 4) com.esri.viewer:该包及其子包的内容是Flex Viewer的主体程序,这里所说的“主体程序”是相对于Widget而言的。主体程序中实现了Widget的基础、对Widget的管理、以及除自定义Widget之外的所有功能,这部分内容将在后边的小节介绍。 5) widgets:Flex Viewer中所有的Widget都在此包中。Widget基于Module开发,Module是Adobe为解决Flex系统体积过大而提出的一种解决方案,较大的Flex系统可以通过Module将系统进行分割,从而减小系统初始化所需加载的体积。Flex Viewer通过Widget将业务功能进行划分,每个Widget都是一个功能相对完善和独立的组件,每个Widget可以独立完成一个或者一组相关操作。 6) libs:这是Flash Builder中Flex工程存放库文件的目录,agslib-2.3-2011-03-01.swc是AGS Flex API 2.3的库文件。根据不同的业务需求,可能会用到更多的库文件,那么这些库文件都将放到这个目录下。 7) locale:Flex Viewer支持国际化,国际化所需的属性文件全部放在该目录下。在Flash Builder中,可通过指定编译参数来决定使用哪种语言,如下图所示: ? 3、???FlexViewer架构分析 ? 在com.esri.viewer.managers包中,汇集了Flex Viewer中所有的Manager。这些Manager各个肩负重任,他们虽隐身幕后,却是Flex Viewer良好运转的关键,我们不妨称之为主席团成员。 1)???? ConfigManager Flex Viewer通过配置文件来组织数据、功能和UI。ConfigManager的责任是适时读取配置文件,对配置文件进行解析,然后将解析结果分发出去,由其他需要使用配置文件数据的模块接收。 2)???? DataManager Flex Viewer各个部分之间需要共享数据,比如说Widget与Widget之间数据共享。DataManager提供了一种数据共享的方案,任何模块都可以通过DataManager把数据贡献出来,供其它模块使用。 DataManager的关键职责是把共享数据保存在内存中,任何时候都可以从DataManager获取需要的共享数据。 3)???? MapManager Map是GIS应用的基础。MapManager解决了Map的问题。MapManager不是对Map的简单封装,而是提供了所有与Map相关的操作,比如根据配置文件加载地图,放大、缩小这些基本操作,画图,在地图上显示信息框,图层控制等等。如果在某个自定义Widget中,想要画一个多边形,不必new一个DrawTool,发个消息告诉MapManager你想画个多边形即可;如果你想在地图上某个点上显示一些信息,同样发个消息告诉MapManager就行了。MapManager会很友好地帮我们完很多工作,我们只需发个消息知会一声。 4)???? ScriptingManager 预留,尚未使用。 5)???? SecurityManager 预留,尚未使用。 6)???? UIManager Flex Viewer用户体验之所以风格统一,是因为UIManager做了大量的工作。在配置文件中,有如下脚本,UIManager会根据这一信息对UI的样式进行配置。 <style> ??????????????<colors> 0xFFFFFF,0x333333,0x101010,0x000000,0xFFD700 </colors> ??????????????<alpha>0.8</alpha> </style> 7)???? WidgetManager 顾名思义,WidgetManager是对Widget进行管理的组件。WidgetManager对Widget的管理包括根据配置文件创建Widget信息列表、加载Widget、布局Widget、关闭Widget等。WidgetManager提供四种Widget布局方式:自由布局(float)、水平布局(horizontal)、垂直布局(vertical)和固定布局(fix)。自由布局Widget可以拖动,水平布局、垂直布局和固定布局Widget完全有WidgetManager管理,不可拖动;四种布局方式中,固定布局Widget不可改变窗口大小。 3.2?Flex Viewer松耦合的关键 系统耦合度是决定系统灵活性与可维护性的关键。Flex Viewer的松耦合设计是其健壮的关键因素之一。那么,是什么保证了FlexViewer的松耦合呢? “事件!事件!还是事件!” 事件是Flex Viewer松耦合的关键。在3.1中多次提到了“消息”,物化到Flex Viewer中就是事件。不同的模块通过事件彼此交互、传递数据,保证了各模块之间的松耦合,彼此不必相知,却能紧密合作。ViewerContainer、EventBus、AppEvent组成了Flex Viewer事件机制的基础。当然,Flex Viewer事件机制的基础是Flex的事件机制。 1)???? EventBus 继承自EventDispatcher,使用了单例模式。EventBus是全局的事件派发器,为Flex Viewer中的不同模块之间的交互提供便利。有了EventBus,不同模块之间的交互无需彼此调用对方的方法,只需派发/监听消息即可。 2)???? AppEvent 继承自Event,在Flex Viewer中被用来当做消息和数据的载体,在不同的模块之间传递数据。AppEvent定义了Flex Viewer中需要的所有事件类型,也就是那些公共的字符串常量。通常,系统层面需要添加的事件也都定义在AppEvent中。 3)???? ViewerContainer 继承自Group,使用了单例模式,是Flex Viewer中各个模块的容器。Flex Viewer中调用ViewerContainer最多的三个方法是:addEventListener()、dispatchEvent()、showError()。我们可以在任何需要对某类AppEvent事件进行监听的地方通过addEventListener()方法添加监听,可以在任何需要派发某类AppEvent事件的地方通过dispatchEvent()方法派发事件,可以在任何需要显示Error信息的地方通过showError()方法显示Error信息。如果通读Flex Viewer代码,会发现,AppEvent的监听与派发随处可见。 Flex Viewer松耦合的关键因素还有Widget的设计和实现,这部分内容将在后面的小节涉及,在此按下不表。 至此,我们的讨论涉及到了Flex Viewer中的绝大部分模块,先来看一下Flex Viewer的整体结构,如图3.1所示。图中最下方的Control Widgets和Business Widgets尚未提及,我们将在3.4中涉及这部分内容。 ? ???????? 图 3.1 Flex Viewer整体结构 3.3?初始化那些事儿 在浏览器地址栏输入Flex Viewer的地址,经过短暂等待,当她华丽丽地展现在我们眼前,你是否想过在这短暂的等待中,Flex Viewer都做了哪些事情呢?本小节我们来探讨Flex Viewer初始化那些事儿。注意,我们这里所说的Flex Viewer初始化,不是Flex概念中组件生命周期的初始化部分,而是指Flex Viewer在可以与用户交互之前,所做的准备工作。 ? 图 3.2 Flex Viewer初始化过程 3.2小节中,我们强调通过使用事件,Flex Viewer将各模块之间充分解耦。实际上,事件也伴随着Flex Viewer初始化的整个过程。Flex Viewer的初始化过程如图3.2所示。 1)???? 首先观察一下ConfigManager的构造方法,其中有这样一句代码: ViewerContainer.addEventListener(ViewerContainer.CONTAINER_INITIALIZED,init); 也就是说,ConfigManager实例在构造期间就通过ViewerContainer在EventBus的单例添加了对CONTAINER_INITIALIZED事件的监听,一旦ViewerContainer在别的地方派发了CONTAINER_INITIALIZED事件,ConfigManager将响应该事件,开始读取配置文件、解析配置文件,构造ConfigData数据,最后将ConfigData连同CONFIG_LOADED事件派发出去,见下面代码: ViewerContainer.dispatchEvent(newAppEvent(AppEvent.CONFIG_LOADED,configData)); 2)???? 我们再观察一下UIManager、WidgetManager和MapManager的代码,在各自的初始化代码中,都可以找到下面一句代码,此处不再解释: ViewerContainer.addEventListener(AppEvent.CONFIG_LOADED,config); 3)???? 最后,问题的关键在于CONTAINER_INITIALIZED事件何时何地被派发?我们观察一下ViewerContainer的init()方法,其中有下面这句代码: ViewerContainer.dispatch(ViewerContainer.CONTAINER_INITIALIZED); 而init()方法是ViewerContainer的creationComplete事件的响应方法。也就是说ViewerContainer初始化结束才是Flex Viewer初始化的开始。 Flex Viewer初始化的脉络已经相当清晰,ConfigManager、UIManager、WidgetManager、MapManager在各自的初始化过程中对相应的事件进行监听,一旦ViewerContainer初始化完成,派发出CONTAINER_INITIALIZED事件,其它的准备工作将顺其自然,水到渠成。 我们注意到,初始化过程所涉及的各个模块都是相互透明的,彼此并不知道有对方的存在,而这些模块之所以能够亲密协作,关键就在于它们都只关心事件,事件机制使得这些模块之间实现松耦合。实际上,事件充斥着Flex Viewer的各个角落,随着对Flex Viewer理解的逐渐深入,对这一点的体会将更加明显。 3.4?Widget设计及实现 ?一个Widget是对一组相关操作的封装,这些相关操作完成某项特定业务功能。Widget不仅可以调用地图资源,也可以访问服务器端资源。Widget基于Flex Module,编译后是独立的swf文件,这样一来,将具体的业务逻辑封装在Widget中,减小了系统主体的体积,在业务功能较多时,可显著缩短系统加载时间。加之面向接口编程和依赖注入思想的应用,Flex Viewer中的Widget可以独立开发,通过配置文件决定提供哪些Widget,从而决定系统提供哪些业务功能。可以把Widget理解为“插件”。这种灵活的“插件”机制是如何实现的呢?下面我们了解一下Widget编程模型,如图3.3所示。 ? ? 图 3.3 Widget编程模型 Widget分为两种,Control Widget和Business Widget。Control Widget是指控制组件,比如NavigationWidget、HeaderControllerWidget、OverviewMapWidget等,这些Widget提供系统级别的功能,不针对具体业务功能;Business Widget是指业务组件,比如SearchWidget、BookmarWidget、GeoRSSWidget等,这些Widget提供具体业务功能。两种Widget都继承自BaseWidget,都由WidgetManager来管理,不同的是,在WidgetManager中有一个WidgetContainer来专门管理Business Widget。 两种Widget有共同的父类BaseWidget,BaseWidget实现了接口IBaseWidget,两者也就有了共同的接口。WidgetManager通过IBaseWidget来管理Widget,与具体的Widget解耦。FlexViewer此处使用面向接口编程和依赖注入,实现了主体系统与Widget的松耦合。 u? IBaseWidget 该接口定义了WidgetManager与Widget进行交互的方法,BaseWidget实现了这个接口。 u? BaseWidget 该类是所有Widget的基类。只要某一组件继承自BaseWidget,WidgetManager即可对其进行管理。由于BaseWidget继承自Module,每一个Widget都将编译成独立的swf文件。 但两种Widget在具体实现上有所不同。Flex Viewer为Business Widget提供了统一的UI基类和接口,也就是WidgetTemplate和IWidgetTemplate。通常情况下,Control Widget都会使用自定义UI,但是这不是必须的。 u? IWidgetTemplate 该接口定义了BaseWidget与WidgetTemplate交互的方法。WidgetTemplate实现了这一接口。 u? WidgetTemplate 该类是IWidgetTemplate的一种默认实现,在具体的Widget实现中,同样可以使用自定义的WidgetTemplate,只要实现IWidgetTemplate接口即可。WidgetTemplate为Widget提供基本UI控件,包括窗口面板、带有图片按钮的标题栏等。使用Flex Viewer实现的WidgetTemplate,开发人员可以将更多的精力和时间放在实现业务逻辑上。 需要说明的是,Widget的设计在Flex Viewer 1.x版本和2.x版本中有所区别。在1.x中不存在ControlWidget,比如菜单组件,菜单项是可配置的,但是菜单组件本身是不可配置的。2.x版本以后,Widget的概念扩大了,Flex Viewer中,凡是能看到的组件都是Widget,这样一来,控制组件可以像业务组件一样可配置,比如HeaderControllerWidget,灵活性大大提高。 ? 4、???FlexViewer2.3自定义Widget 1)? 安装Flash Builder,下载Flex Viewer源码,我们使用最新的2.3; 2)? 打开Flash Builder,导入Flex Viewer 2.3的源代码; 3)? 鼠标放在widgets包上,单击右键,在弹出的菜单中选择New,然后单击MXML Component; ? 4)? 在New MXML Component对话框中,输入包名“widgets.HelloWorld”,填写Widget名称“HelloWorldWidget”,并选择基类BaseWidget,单击Finish;? 5)? 此时,HelloWorldWidget已经创建完毕,按照Flex Viewer提倡的做法,在其包下新建一个同名xml配置文件,即“HelloWorldWidget.xml”; 6)? 此时,HelloWorldWidget不会被编译,因为还未把它加入到Module列表。打开工程的属性窗口,点击Flex Modules,点击Add键,将HelloWorldWidget加入到Module列表中。点击OK,会发现HelloWorldWidget的图标已经和其他的Widget一样; ? 7)? 启动编译,编译后会发现在bin-debug目录下,HelloWorldWidget已经被编译成swf文件; 8)? 下面开始实现HelloWorldWidget的业务逻辑,在Widget面板中显示出配置文件中的“Hello,Flex Viewer!”字符串。配置文件内容: <?xml version="1.0" ?> <configuration> <hellocontent>Hello,Flex Viewer!</hellocontent> </configuration> HelloWorldWidget代码: <?xml version="1.0" encoding="utf-8"?> <viewer:BaseWidgetxmlns:fx="http://ns.adobe.com/mxml/2009" ?????????????????? ??xmlns:s="library://ns.adobe.com/flex/spark" ?????????????????? ??xmlns:mx="library://ns.adobe.com/flex/mx" ?????????????????? ?? xmlns:viewer="com.esri.viewer.*" ?????????????????? ?? layout="absolute" ?????????????????? ?? width="400" ?????????????????? ?? height="300" ?????????????????? ?? widgetConfigLoaded="init()"> ???? <fx:Script> ???????? <![CDATA[ ????????????? [Bindable] ????????????? private varhelloContent:String; ????????????? privatefunction init():void{ ?????????????????? if(configXML){ ?????????????????????? helloContent=String(configXML.hellocontent); ?????????????????? } ????????????? } ???????? ]]> ???? </fx:Script> ???? <viewer:WidgetTemplate> ???????? <s:HGroupwidth="100%" ?????????????????? ? height="100%" ?????????????????? ? horizontalAlign="center" ?????????????????? ? verticalAlign="middle"> ????????????? <s:Labeltext="{helloContent}"/> ???????? </s:HGroup> ???? </viewer:WidgetTemplate> </viewer:BaseWidget> 9)? 在config.xml对HelloWorldWidget进行配置,如下: <widgetcontainer> <widget label="HelloWorld"icon="assets/images/i_solar.png" ????????? config="widgets/HelloWorld/HelloWorldWidget.xml" ????????? url="widgets/HelloWorld/HelloWorldWidget.swf"/> </widgetcontainer> 10)编译,运行!Hello,Flex Viewer! ? ? 5、???FlexViewer2.3Widget之间的通信 虽然每个Widget都是封装良好的一个组件,提供一组针对特定业务功能的操作,但是有时候需要Widget之间的彼此协作来完成一个粒度更大的业务逻辑。此时就需要Widget之间的交互,或者说通信。经常看到这样的问题“一个Widget如何调用另外一个Widget的方法?”。Widget之间彼此相互独立,互不知晓,“一个Widget调用另一个Widget的方法”意味着两个Widget紧密地耦合在了一起,这不符合“松耦合”的要求。那么Widget之间该如何交互呢?答案还是事件!还记得ViewerContainer的addEventListener()和dispatchEvent()方法么?ViewerContainer通过EventBus添加监听和派发事件,使各模块默契协作。 5.1? 直接通信 直接通信是指Widget A监听某一事件E,Widget B在任何时间派发了事件E,WidgetA都能够捕捉到这个事件,并进行响应。当然,这里对事件的监听和捕捉都是通过ViewerContainer代理的EventBus实现的,参见图5.1。我们注意到,Widget A与Widget B除了通过全局的EventBus监听、派发、捕捉事件外,没有发生任何直接的联系。 图 5.1 Widget之间直接通信 ①??? ?Widget A通过EventBus监听事件E; ②??? ?Widget B通过EventBus派发事件E; ③??? ?Widget A通过EventBus捕捉事件E,并进行响应。 5.2? 间接通信 直接通信有一个前提条件,那就是必须交互的两个Widget都已经被加载。Widget采用lazy-load的加载方式,也就是只有第一次打开的时候才会加载。如果Widget B已经加载并派发了事件E,而此时Widget A还并未加载,Widget A是无法捕捉并响应事件E的,因为Widget A在内存中还不存在。 此时的解决方法是通过DataManager间接通信。DataManager响应Widget B派发的事件,并数据存储起来,Widget A在第一次加载时通过DataManager获取Widget B共享的数据,以此可以达到通信的目的,参见图5.2。同样的,Widget A与WidgetB,以及两者与DataManager之间并未直接交互,所有的协作都通过事件来完成。 ? ①??? ?DataManager通过EventBus监听共享数据事件和获取数据事件; ②??? ?Widget B派发共享数据事件; ③??? ?DataManager响应共享数据事件,将数据以key-value的形式保存; ④??? ?Widget A监听分发数据事件,并且派发获取数据事件; ⑤??? ?DataManager响应获取数据事件,查询Widget A所需数据,派发分发数据事件; ⑥??? ?Widget A响应分发数据事件,并做相应的响应动作。 DataManager是Flex Viewer为模块之间共享数据和保存共享数据设计的一种解决方案。DataManager将各个模块需要共享出来的数据存储在内存中,一旦某个模块需要获取共享数据,派发一个消息即可,DataManager会响应这个消息,并共享数据作为消息的载体派发出去。 DataManager响应的事件 u? DATA_FETCH_ALL:获取数据事件(全部数据) u? DATA_PUBLISH:共享数据事件 u? DATA_FETCH:获取数据事件(根据key获取数据) DataManager派发的事件 u? DATA_SENT:分发数据事件 u? DATA_NEW_PUBLISHED:当有新的数据被共享时,将新数据分发出去 ? ? 6、???FlexViewer2.3中拉帘Widget下载 由于最新的ArcGIS API for flex2.x使用了Flex SDK4,因此造成了对之前ArcGIS API for flex1.x制 作的拉帘Widget无法使用,因此重新制作了拉帘工具供大家使用。 下载地址: Swipe 使用方法:将下载后的swipe.zip解压,将swipe目录拷贝到flexViewer工程的srcwidgets目录下,并 将Widget加入到flexViewer工程的模块(Flex Module)中,编译。最后在congfig.xml中增加为 swipeWidget的引用。 截图如下: (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |