使用框架:AS3
任務描述:瞭解PureMVC框架使用方式,瞭解普通AS3使用HTTP請求進行網絡交互的方式,理解PureMVC框架設計思想
難度係數:2php
本章源碼下載:http://www.iamsevent.com/zb_users/UPLOAD/learnPureMVC1/WeatherTest.rar程序員
最近在討論羣裏常常有看見有新手想學習PureMVC,爲何想學呢?由於不少作頁遊的公司都在AS工程師的招聘啓事裏寫「熟悉PureMVC」這一條,因此爲了找到工做,也無論三七二十一,先準備把PureMVC學起來再說。可是直接一上手就看官方的文檔,很難領會其中奧妙,在天地會上面的PureMVC專題找到的學習資料又比較雜,那麼在此特推出一篇教程供你們學習一下。web
先簡單闡述一下PureMVC中的幾大要點吧:api
①PureMVC中存在一個叫作Facade的類,它是PureMVC的核心類,負責管理調度PureMVC中的成員交互和運做。通常來講,一個應用中建立一個facade對象就夠了。數組
②PureMVC中主要有三個角色:Mediator、Command以及Proxy。這三個角色分別對應着咱們一般所說的MVC思想中的V(視圖)、C(控制器)、M(數據管理器)。它們只有被註冊到facade類中才能運做,纔有意義。經過調用facade對象的registerMediator、registerCommand或registerProxy方法能夠將它們註冊到facade對象中去。一旦被註冊到facade對象中去了以後,享用同一註冊源的Mediator、Command以及Proxy對象就能夠經過notification(通知)進行彼此之間的通信,notification相似於AS中的事件,只不過notification是在全局進行派發罷了,而事件只有事件派發者本人或其顯示列表的父容器才能偵聽到。使用notification進行通信是PureMVC最大的便利之處。在一個Mediator、Command或者Proxy對象中使用sendNotification方法能夠派發一個notification通知:安全
sendNotification("MyNotification");
該方法的首個參數表明該條notification的名字,確保notification的名字的惟一性是減小意外錯誤的最佳保證。若是你願意,你能夠爲該方法傳入第二個參數做爲該條notification所攜帶的數據,能夠是一個String, int或者是Object、Array等複雜對象。網絡
notification的派發方式是同樣的,可是接受方式有些不一樣,對於Mediator對象來講,咱們須要經過重載listNotificationInterests方法爲它列出其感興趣的notification列表,若是在一個notification被髮出時,該Mediator對象經過查看該notification的名字來判斷該條notification是不是本身所感興趣的,若感興趣,則會調用handleNotification方法來對該notification進行處理:mvc
override public function listNotificationInterests():Array { return ["MyNotification", "Shit"]; } override public function handleNotification(notification:INotification):void { switch( notification.getName() ) { case "MyNotification": trace("get MyNotification"); break; case "Shit": trace(notification.getBody()); break; } }
listNotificationInterests方法的返回值必須是一個數組,並且該數組中的內容不能動態改變。當一個Mediator對多個notification感興趣的時候,能夠在handleNotification方法中使用switch...case語句針對不一樣名字的notification執行不一樣的邏輯,如果在派發通知時在sendNotification方法中設置了第二個參數,那麼能夠在處理通知時使用notification對象的getBody方法來獲取通知所攜帶的數據。app
對於Command對象,若要讓它接受notification通知,就必須爲該Command在facade中註冊notification對應關係:框架
registerCommand("Shit", ShitCommand); registerCommand("Fuck", FuckCommand);
一個Command須要響應多少種類的notification就須要多少條註冊語句,注意Command的使用方法,咱們沒有必要去實例化一個Command對象,而僅僅須要建立一個Command類,以後爲該類註冊notification響應關係便可。註冊完畢以後咱們須要在Command類中經過重載execute方法來爲Command編寫通知處理邏輯:
override public function execute(notification:INotification):void { switch( notification.getName() ) { case "Shit": trace("get Shit"); break; case "Fuck": trace("get Shit"); break; } }
其中邏輯基本上與Mediator的處理模式是同樣的。對於Proxy來講,它不具有響應notification的能力,這樣也使得其Model(數據管理者)的角色更加純粹一些,數據管理者自己就沒有必要參與到應用程序的業務邏輯中去。
一旦某個M、C或者P被註冊到facade中,它們中的facade屬性就會持有註冊到的facade對象的引用,而後咱們能夠在任意時候調用該屬性的retrieveMediator方法獲取到某個名稱對應的Mediator對象。如咱們使用剛纔註冊的那個名字去facade中取到相應的Mediator對象:
var mediator:Mediator = facade.retrieveMediator("SpMediator");
相似地,咱們可使用使用facade屬性的retrieveProxy方法獲取到指定名稱對應的Proxy對象。這是除了notification外的另外一種通信方式,只不過該方式耦合性稍高一些。若是在某些狀況下Mediator要獲取Proxy的數據,就能夠經過該方法拿到。
③關於這三者的使用方法,在AS項目中,咱們會爲一個視圖模塊套上一個Mediator對象。相似這樣:
var sp:Sprite = new Sprite(); var mediator:Mediator = new Mediator("SpMediator", sp);
Mediator的構造函數默認接受兩個參數,第一個指的是該Mediator的名字,一旦被指定了一個名字,該Mediator對象就會以該名字被註冊到facade中去;第二個參數則是該Mediator所關聯的視圖對象,關聯了一個視圖對象以後,Mediator能夠經過其viewComponent屬性來訪問它所關聯着的視圖對象。
經過爲Flash顯示列表中某個顯示對象創建與Mediator的關聯,咱們這就讓顯示列表與PureMVC框架之間銜接了起來。在Mediator中對其所關聯顯示對象viewComponent偵聽事件,咱們就能夠在viewComponent須要獲取數據或作一些其自身所沒法完成的大事時藉助PureMVC的力量予以完成掉。下圖展現的是一個項目中,Flash顯示列表與PureMVC之間的通信關係:
由此圖咱們能夠看到,在顯示列表中的顯示對象在外圍包裹了一層Mediator以後,若是它須要獲取數據,只須要派發一個事件便可,顯示對象自身無需知道數據獲取的過程,它只是衣來伸手飯來張口,我TM要數據的時候你TM就乖乖給我拿過來,老子無論你用了什麼方式!如果你已準確地在Mediator中爲相應事件添加了事件偵聽器,那麼在收到來自其所關聯的顯示列表中派發出來的事件後就能夠開始經過notification讓PureMVC框架運做起來了,若要獲取數據,那麼該notification會被Command相應並調用Proxy的API,由Proxy負責網絡交互,待取到數據後使用notification發送回Mediator;若要切換面板(如點擊顯示列表中某個按鈕後打開另外一個面板),則發送的notification會被要切換到的面板所關聯的Mediator相應並執行相關切換面板的操做。
光看文字有點抽象,可是我也不太可能出視頻。因此只能結合一點實例來理論結合實踐一下。因爲我本身不會寫後臺,因此只能找網上一些公開的API,用得比較多的一個是雅虎的天氣API,這個的使用方法也比較簡單,稍後咱們就會看到。在本教程中,因爲安全沙箱的問題,不能將演示的swf放上來,所以還得列位下載源碼後在本身的機器上編譯運行以查看結果,如今只放出一個運行結果的截圖以供列位在腦中有個小小的印象:
很簡單對吧?只須要獲取一下天氣信息再顯示出來就行了,數據和圖片都從雅虎網站上加載,所以要看到效果,必須讓你的機器處於聯網狀態才行。
好了,搓搓手,let`s fucking start our game, baby! 首先是建立文檔類,而後在文檔類中,若要啓動PureMVC框架,就必須建立一個facade實例。因爲咱們當前作的是一個天氣預報程序,因此我們的facade類就取名叫WeatherFacade好了:
package com.iamsevent { import com.iamsevent.control.commands.ApplicationCommand; import org.puremvc.as3.patterns.facade.Facade; public class WeatherFacade extends Facade { private static var _instance:WeatherFacade; public function WeatherFacade() { super(); } public static function get instance():WeatherFacade { if( !_instance ) { _instance = new WeatherFacade(); } return _instance; } override protected function initializeController():void { super.initializeController(); //pureMVC一啓動時就須要註冊一些會用到的command this.registerCommand(NotificationDictionary.STARTUP_APP, ApplicationCommand); } public function startup():void { sendNotification(NotificationDictionary.STARTUP_APP); } } }
在建立了facade以後,咱們想把一些初始化工做放到一個Command裏去作,由於初始化的代碼不涉及到Mediator和Proxy。可是以前說過,在PureMVC中,一個Command只有被註冊到了facade中才有意義,才能運做。在facade啓動時會調用其一個名叫initializeController的方法來執行一系列Command的註冊工做,咱們就把作初始化工做的Command(我這裏命名爲ApplicationCommand)在該方法中執行註冊工做。註冊完畢後,咱們讓WeatherFacade開放一個公共API出來,以便外部在想要啓動facade的時候能夠隨時啓動。我將該API命名爲startup,在該方法中只執行一句代碼,就是派發咱們以前註冊的與ApplicationCommand相關聯的通知(這裏出現的NotificationDictionary類是一個我建立的用於記錄該應用中全部可用的notification名字的類)。在派發該通知以後會發生什麼事?對,這位同窗回答得很好,會執行與它關聯的Command的excute方法。下面給出的是ApplicationCommand類的代碼:
public class ApplicationCommand extends SimpleCommand { public function ApplicationCommand() { super(); } override public function execute(note:INotification):void { trace("application startup!"); } }
在pureMVC中提供了兩種類型的Command以供咱們選擇:MacroCommand和SimpleCommand,前者可讓你一次性按順序執行多個Command,後者則一次只執行一條Command,總而言之,SimpleCommand是單一命令,MacroCommand是多命令。在個人實際應用中,基本上極少會用到前者,通常使用後者就足以知足咱們的使用須要了。在ApplicationCommand的excute方法中咱們只執行一句踹死語句,這意味着如今,當咱們經過調用咱們的WeatherFacade.startUp方法啓動應用後,會看到控制檯輸出一句"application startup",除此以外不會發生任何其餘事情。稍後,咱們會在該excute方法中添加其它的一些邏輯。如今,咱們先建立出咱們的文檔類WeatherTest.as,並試着啓動咱們的WeatherFacade看看。
package { import com.iamsevent.WeatherFacade; import flash.display.Sprite; import flash.system.System; [SWF(width="600", height="500")] public class WeatherTest extends Sprite { public function WeatherTest() { System.useCodePage = true; WeatherFacade.instance.startup(); } } }
因爲WeatherFacde被我作成了單例,因此咱們能夠在文檔類中很方便地拿到其全局惟一的實例並執行其啓動方法。我這裏還使用了一句Systen.useCodePage = true 是爲了讓我在向雅虎獲取天氣數據時不至於出現亂碼。若是你如今執行咱們的文檔類WeatherTest,你將會如期看到控制檯輸出了一條應用已啓動的消息。到此爲止,列位應該已經瞭解了pureMVC中Command和notification的使用方式了。那麼接下來,讓咱們繼續來點好玩的,Come on!
咱們知道,咱們在作Flash應用的時候一般會把文檔類做爲最上層父容器,其餘建立的圖形啊面板什麼的都會addChild在文檔類或其子對象上面。那麼在pureMVC中,爲了延續咱們的習慣,咱們須要讓啓動應用的Command——ApplicationCommand持有文檔類的引用才行。爲了達到這個目的,我在WeatherFacade.startup方法中添加了一個WeatherTest類型的參數,並將該參數放在notification中攜帶數去
public function startup(main:WeatherTest):void { sendNotification(NotificationDictionary.STARTUP_APP, main); }
以後,在ApplicationCommand的excute方法中取出其接收到的notification對象攜帶的文檔類引用並持有之:
public class ApplicationCommand extends SimpleCommand { private var _main:WeatherTest; …… override public function execute(note:INotification):void { _main = note.getBody() as WeatherTest; } }
最後,咱們只須要在文檔類中,將文檔類自身的引用傳遞給WeatherFacade.startup方法就好了。
public class WeatherTest extends Sprite { public function WeatherTest() { System.useCodePage = true; WeatherFacade.instance.startup(this); } }
在ApplicationCommand持有了文檔類的引用以後咱們就能夠在ApplicationCommand.excute方法中執行一系列咱們熟悉的addChild及removeChild等操做來爲舞臺上添置東東了。在添置東東以前,咱們先想一想,咱們舞臺上要放置點神馬東西呢?首先,是一個面板,上面有多個按鈕,每一個按鈕表明一個城市,我點擊哪一個城市的按鈕就能夠查詢哪一個城市的天氣預報信息!爲了簡便起見,我使用了之前教程中用到的一些按鈕、面板組件,源碼就不放出了,感興趣的道友能夠直接下載源碼進行查看。咱們首先建立的導航面板NavigationPanel代碼以下:
/** * 導航面板,用以選擇查詢的城市 * @author S_eVent * */ public class NavigationPanel extends Canvas { private var _buttonList:Vector.<CustomButton> = new Vector.<CustomButton>(); private var _gap:int = 10;//按鈕間水平間距 public function NavigationPanel() { super(); this.mouseEnabled = false; //對於不常常用到的事件偵聽使用弱引用(addEventListener方法第五個參數設爲true) this.addEventListener(Event.ADDED_TO_STAGE, onAdded, false, 0, true); } /** * 設置按鈕項 * @param items 按鈕項數據提供源。其中的元素格式需是包含有label(按鈕標籤)及name(用以在查詢天氣時使用的名字)屬性的Object對象 * */ public function setItems(items:Array):void { //清空按鈕列表 while(_buttonList.length > 0) { removeChild(_buttonList.pop()); } var button:CustomButton; for each(var elem:Object in items) { button = new CustomButton(elem.label); button.name = elem.name; addChild(button); _buttonList.push( button ); } updateDisplayList(); } /** 更新顯示列表,讓其中組件按必定方式排布 */ public function updateDisplayList():void { //對於NavigationPanel來講,它的排列規則是讓其中的按鈕都居中排布 //計算所有按鈕排布後的寬度佔用 var w:Number = 0; var len:int = _buttonList.length; var i:int; for(i=0; i<len; i++) { w += _buttonList[i].width; } w += (len - 1) * _gap; //計算所有按鈕排布後的高度佔用 var h:Number = _buttonList[0].height; //獲得起始位置後開始排列 var startX:Number = (this.width - w) / 2; var startY:Number = (this.height - h) / 2; for(i=0; i<len; i++) { _buttonList[i].x = startX; _buttonList[i].y = startY; startX += _buttonList[i].width + _gap; } } private function onAdded( e:Event ):void { //因爲CLICK事件是冒泡事件,因此能夠在父類註冊一個點擊偵聽器來偵聽該面板內全部子組件的點擊事件,因爲在該類構造函數中 //已經將this.mouseEnble設爲了false,因此只有點擊該類的子顯示對象纔可能觸發CLICK事件, //排除了該類的背景被點擊後會派發CLICK事件產生的干擾 this.addEventListener(MouseEvent.CLICK, onClick); this.addEventListener(Event.REMOVED_FROM_STAGE, onRemoved); } private function onClick( e:MouseEvent ):void { //中止CLICK事件冒泡以保證不會對外部的事件偵聽形成干擾 e.stopPropagation(); var btn:CustomButton = e.target as CustomButton; //派發一個冒泡事件通知外部有一個按鈕被點擊了,被點擊的按鈕的名字將會被存入事件的data屬性中 //該冒泡事件將在WeahterPanelMediator中被偵聽 dispatchEvent(new CustomEvent(CustomEvent.ITEM_SELECTED, btn.name, true)); } private function onRemoved( e:Event ):void { this.removeEventListener(MouseEvent.CLICK, onClick); this.removeEventListener(Event.REMOVED_FROM_STAGE, onRemoved); } }
首先,該導航面板類繼承自Canvas類,Canvas類是我自定義的一個繼承自Sprite的類,它實現了面板的一些基本功能,包括繪製背景,設置固定大小的功能。在導航面板的構造函數中我將它的mouseEnable設置爲了false,這是爲了使我在爲它添加鼠標點擊事件的時候,事件對象的target屬性不會指向它自身,這樣就保證了我每次只有點擊其內部的子顯示對象纔會觸發CLICK事件,我點擊子顯示對象區域以外的背景區域都不會派發CLICK事件了,這樣就沒必要爲每一個子顯示對象都註冊一個CLICK事件偵聽,只要一個事件偵聽器就能夠達到偵聽點擊事件的目的。這也是我經常使用的一個小技巧。
另外,NavigationPanel的setItems方法可讓咱們很方便地建立出將出如今導航面板中的按鈕項目,咱們只須要提供一個知足必定格式的數組(在setItems方法的註釋中有說明),導航面板就會根據該數據來建立出相應的按鈕並自動調用updateDisplayList方法來實現排版工做(CustomButton類是一個灰底黑字的按鈕組件,在構造函數中傳入的是一個表明按鈕文字的String對象)。
好了,建立完了導航面板後,咱們還須要一個用來顯示某個城市詳細天氣信息的信息面板,該面板將會在我點擊導航面板中某個城市的按鈕後打開,它的效果圖如一開始給出的應用預覽圖一致。先上完整代碼:
package com.iamsevent.view.component { import com.iamsevent.model.events.CustomEvent; import com.iamsevent.model.vo.ForcastVO; import flash.display.Loader; import flash.events.Event; import flash.events.MouseEvent; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; /** * 顯示天氣預報詳情的面板 * @author S_eVent * */ public class InformationPanel extends Canvas { private var _image:Loader = new Loader(); private var _currentInfo:ForcastView; private var _forcastInfo:Vector.<ForcastView>; private var _backBtn:CustomButton; private var _title:TextField; private var _forcastNum:int = 2; private var _waitingText:TextField; public function InformationPanel() { super(); _title = new TextField(); _title.mouseEnabled = false; _title.autoSize = TextFieldAutoSize.LEFT; _title.defaultTextFormat = new TextFormat("SimSun", 14, 0, true); addChild(_title); _currentInfo = new ForcastView(); var view:ForcastView; _forcastInfo = new Vector.<ForcastView>(); for(var i:int=0; i<_forcastNum; i++) { view = new ForcastView(); addChild(view); _forcastInfo[i] = view; } addChild(_currentInfo); _backBtn = new CustomButton("返回"); addChild(_backBtn); _waitingText = new TextField(); _waitingText.mouseEnabled = false; _waitingText.autoSize = TextFieldAutoSize.LEFT; _waitingText.defaultTextFormat = new TextFormat("SimSun", 20, 0, true); _waitingText.text = "正在加載……" addChild(_waitingText); _waitingText.visible = false; addEventListener(Event.ADDED_TO_STAGE, onAdded, false, 0, true); } /** * 設置一個城市的天氣預報信息 * @param info 一個保存天氣預報信息向量。其中第一個元素爲當前天氣情況,後四個元素爲從今天起4天的天氣狀況 * */ public function setInfomation(info:Vector.<ForcastVO>):void { _currentInfo.forcastVO = info[0]; for(var i:int=0; i<_forcastNum; i++) { _forcastInfo[i].forcastVO = info[i+1]; } layout(); } /** * 顯示/隱藏加載文字 * @param show 是否顯示加載文字 * */ public function showWaitingText(show:Boolean):void { if( show ) { _waitingText.visible = true; _waitingText.x = (this.width - _waitingText.width) / 2; _waitingText.y = (this.height - _waitingText.height) / y; } else { _waitingText.visible = false; } } private function layout():void { var estimateW:Number = 150; var estimateH:Number = 150; var commonY:Number = (this.height - estimateH) / 2; var currentX:Number = (this.width - (estimateW * (1+_forcastNum))) / 2; _currentInfo.y = commonY; _currentInfo.x = currentX; currentX += estimateW; for(var i:int=0; i<_forcastNum; i++) { _forcastInfo[i].x = currentX; _forcastInfo[i].y = commonY; currentX += estimateW; } } private function onAdded( e:Event ):void { _backBtn.addEventListener(MouseEvent.CLICK, onClick); this.addEventListener(Event.REMOVED_FROM_STAGE, onRemoved); } private function onRemoved( e:Event ):void { _backBtn.removeEventListener(MouseEvent.CLICK, onClick); this.removeEventListener(Event.REMOVED_FROM_STAGE, onRemoved); } private function onClick( e:MouseEvent ):void { dispatchEvent(new CustomEvent(CustomEvent.CHANGE_STATE, null, true)); } override public function set width(value:Number):void { super.width = value; _backBtn.x = width - _backBtn.width; } override public function set height(value:Number):void { super.height = value; _backBtn.y = height - _backBtn.height; } /** 信息面板左上角標題文字 */ public function get title():String { return _title.text; } public function set title(value:String):void { _title.text = value; } } } import com.iamsevent.model.vo.ForcastVO; import com.iamsevent.view.component.CustomButton; import com.iamsevent.view.global.Definition; import flash.display.Loader; import flash.display.Sprite; import flash.events.Event; import flash.net.URLRequest; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.text.TextFormat; class ForcastView extends Sprite { private var _image:Loader = new Loader(); private var _dayTF:TextField = new TextField(); private var _conditionTF:TextField = new TextField(); private var _forcastVO:ForcastVO; private var _request:URLRequest = new URLRequest(); private var _gap:int = 2; public function ForcastView() { _dayTF.defaultTextFormat = new TextFormat("SimSun", 12, 0, true); _conditionTF.defaultTextFormat = new TextFormat("SimSun", 12, 0); this.mouseChildren = false; _dayTF.autoSize = _conditionTF.autoSize = TextFieldAutoSize.LEFT; addChild(_dayTF); addChild(_image); addChild(_conditionTF); } public function refresh():void { _image.unload(); var dayText:String = "", conditionText:String = ""; if( _forcastVO ) { _request.url = Definition.IMAGE_URL_PREFIX + _forcastVO.iconPath + ".gif"; _image.load( _request ); _image.contentLoaderInfo.addEventListener(Event.COMPLETE, onComp); if( _forcastVO.isCurrent ) { dayText += "當前"; conditionText += _forcastVO.temp; conditionText += "\n" + _forcastVO.condition; } else { dayText += _forcastVO.day; conditionText += _forcastVO.lowTemp + "~" + _forcastVO.highTemp; conditionText += "\n" + _forcastVO.condition; } } _dayTF.text = dayText; _conditionTF.text = conditionText; layout(); } private function layout():void { _dayTF.y = 0; _image.y = _dayTF.height + _gap; _conditionTF.y = _image.y + _image.height + _gap; _conditionTF.y + _conditionTF.height + _gap; } private function onComp( e:Event ):void { _image.contentLoaderInfo.removeEventListener(Event.COMPLETE, onComp); layout(); } /** 天氣預報數據 */ public function get forcastVO():ForcastVO { return _forcastVO; } public function set forcastVO(value:ForcastVO):void { _forcastVO = value; refresh(); } }
咋看之下代碼不少,可是細細看來,其實也沒多少東西。以上代碼中除了信息面板之身以外還包含了一個叫作ForcastView的包外類,該類將被用以表示天天的天氣信息(包括天氣圖標,溫度、天氣描述文本),我想顯示幾天的天氣信息,就建立幾個ForcastView對象。該對象的數據提供源來自一個ForcastVO類,該類中記錄了以下幾個信息:
package com.iamsevent.model.vo { /** * 天氣預報數據對象 * @author S_eVent * */ public class ForcastVO { /** 最低溫度 */ public var lowTemp:int; /** 最高溫度 */ public var highTemp:int; /** 天氣條件 */ public var condition:String; /** 當前溫度(攝氏度) */ public var temp:int; /** 天氣圖標路徑 */ public var iconPath:String; /** 星期幾 */ public var day:String; /** 該ForcastVO對象記錄的是不是當前天氣信息 */ public var isCurrent:Boolean; } }
當isCurrent屬性爲true時,ForcastView的時間部分將顯示爲「當前」,溫度只顯示當前溫度;不然,時間部分將顯示對應星期幾,溫度會顯示最高到最低溫度。因爲該類只會在InformationPanel中用到,我就直接把它做爲包外類了。有了該類以後,我在InformationPanel中建立了三個它的對象,一個用來表示當前天氣信息,剩下兩個用來表示接下來兩天的天氣預報信息。另外,我還建立了一個「返回」按鈕用以回到導航面板,點擊它以後將會派發一個事件,該事件將會被InformationPanel的父類偵聽並處理。若是要設置InformationPanel的天氣信息,調用setInfomation方法便可達到目的,若是要讓咱們在加載時顯示等待文字,能夠調用showWaitingText方法。
接下來,咱們須要一個來包含NavigationPanel及InformationPanel,並根據用戶的交互來適時切換兩個面板的顯示狀態。爲此,咱們建立了WeatherPanel類:
package com.iamsevent.view.component { import com.iamsevent.model.events.CustomEvent; import com.iamsevent.model.vo.ForcastVO; public class WeatherPanel extends Panel { /** 導航狀態 */ public static const STATE_NAVIGATION:int = 1; /** 信息狀態 */ public static const STATE_INFORMATION:int = 2; private var _navigationPanel:NavigationPanel; private var _infomationPanel:InformationPanel; private var _currentState:int; public function WeatherPanel(width:Number, height:Number, backgroundColor:uint=0x000000, backgroundAlpha:Number=1, title:String = "", dragable:Boolean = false) { super(width, height, backgroundColor, backgroundAlpha, title, dragable); _navigationPanel = new NavigationPanel(); _infomationPanel = new InformationPanel(); _navigationPanel.width = _infomationPanel.width = width; _navigationPanel.height = _infomationPanel.height = height; addEventListener(CustomEvent.CHANGE_STATE, onChangeState, false, 0, true); addEventListener(CustomEvent.ITEM_SELECTED, onItemSelected, false, 0, true); } /** * 設置按鈕項 * @param items 按鈕項數據提供源。其中的元素格式需是包含有label(按鈕標籤)及name(用以在查詢天氣時使用的名字)屬性的Object對象 * */ public function setItems( items:Array ):void { _navigationPanel.setItems(items); } /** * 設置一個城市的天氣預報信息 * @param info 一個保存天氣預報信息向量。其中第一個元素爲當前天氣情況,後四個元素爲從今天起4天的天氣狀況 * */ public function setInfomation( info:Vector.<ForcastVO> ):void { _infomationPanel.setInfomation(info); } /** * 顯示/隱藏加載文字 * @param show 是否顯示加載文字 * */ public function showWaitingText(show:Boolean):void { _infomationPanel.showWaitingText(show); } private function onChangeState( e:CustomEvent ):void { currentState = WeatherPanel.STATE_NAVIGATION; } private function onItemSelected( e:CustomEvent ):void { currentState = WeatherPanel.STATE_INFORMATION; _infomationPanel.title = e.data.toString(); } /** 天氣面板當前狀態。可選值爲WeatherPanel中以STATE開頭的常量 */ public function get currentState():int { return _currentState; } public function set currentState(value:int):void { if( _currentState != value ) { _currentState = value; if( this.contains(_navigationPanel) ) this.removeChild(_navigationPanel); if( this.contains(_infomationPanel) ) this.removeChild(_infomationPanel); if( _currentState == STATE_INFORMATION ) { this.addChild(_infomationPanel); } else if( _currentState == STATE_NAVIGATION ) { this.addChild(_navigationPanel); } } } } }
做爲一個面板管理者,它不須要具有太多的功能,主要職責就是偵聽來自子面板派發的冒泡事件並執行相應的面板顯示狀態切換就行了。WeatherPanel繼承自Panel類,而Panel類又繼承自Canvas,所以Panel類擁有Canvas的所有功能(能繪製背景,可設置固定尺寸),而且還增長了一個標題欄,拖拽標題欄能夠帶動整個面板的移動。
好了,看完了乏味的幾個視圖類以後總算等到了咱們PureMVC中幾員大將的登場。以前說過,一個視圖模塊要想與外部通信,必須經過該視圖模塊外層嵌套的Mediator來代理。那麼爲此,咱們隆重介紹一下寡人的愛將——WeahterPanelMediator。
package com.iamsevent.view.mediator { import com.iamsevent.NotificationDictionary; import com.iamsevent.model.events.CustomEvent; import com.iamsevent.model.vo.ForcastVO; import com.iamsevent.view.component.WeatherPanel; import org.puremvc.as3.interfaces.INotification; import org.puremvc.as3.patterns.mediator.Mediator; public class WeahterPanelMediator extends Mediator { public static const NAME:String = "WeahterPanelMediator"; //去掉構造函數的第一個參數,讓該Mediator的名字恆定爲NAME常量定義的字符串。將第二個參數定死爲WeatherPanel類型, //讓該Mediator只能關聯WeatherPanel類型的對象上 public function WeahterPanelMediator(viewComponent:WeatherPanel=null) { super(NAME, viewComponent); } override public function onRegister():void { weatherPanel.setItems( [{label:"北京", name:"Beijing"}, {label:"上海", name:"Shanghai"}] ); weatherPanel.currentState = WeatherPanel.STATE_NAVIGATION; weatherPanel.addEventListener(CustomEvent.ITEM_SELECTED, onItemSelected, false, 0, true); } override public function listNotificationInterests():Array { return [NotificationDictionary.ON_GET_WEATHER]; } override public function handleNotification(notification:INotification):void { weatherPanel.setInfomation( notification.getBody() as Vector.<ForcastVO> ); weatherPanel.showWaitingText(false); } private function onItemSelected( e:CustomEvent ):void { sendNotification(NotificationDictionary.GET_WEATHER, e.data); weatherPanel.showWaitingText(true); } //定義此get方法以便於更加方便地拿到此Mediator所關聯的WeatherPanel類型, //不用每次都要將viewComponent變量as成WeatherPanel類型 private function get weatherPanel():WeatherPanel { return viewComponent as WeatherPanel; } } }
我在關鍵部分都寫了註釋,另外,在Mediator被註冊後會當即調用onRegister方法,咱們能夠在該方法中寫一些初始化的代碼,在本例中,我爲其所關聯的WeatherPanel類進行了如下初始化工做:
1.設置導航面板中只顯示兩個選項:北京和上海;
2.切換其初始化視圖狀態爲「導航面板」顯示狀態
3.偵聽導航面板中某選項被選擇事件
在某選項被選擇事件CustomEvent.ITEM_SELECTED被派發後,WeahterPanelMediator將會派發出一個名爲NotificationDictionary.GET_WEATHER的notification,這個通知派發出去後會發生什麼事情,咱們尚且無論,反正我WeahterPanelMediator只知道,我要獲取天氣預報的數據就必須派發這個通知出去(同時將我要獲取的天氣預報城市名放在notification中攜帶出去)。而後等天氣預報數據獲取到以後會收到一個名爲NotificationDictionary.ON_GET_WEATHER的通知,我將該通知列爲WeahterPanelMediator所感興趣的通知(在listNotificationInterests方法中列出),並在通知處理方法handleNotification中將獲取到的天氣數據傳遞給其所關聯的weatherPanel去顯示。
接下來,咱們要面對的問題是,讓誰去響應WeahterPanelMediator發出的NotificationDictionary.GET_WEATHER這個通知。以前咱們說過,Proxy沒法直接對notification作出響應,那麼此時咱們就須要一個Command來作中介了。爲此,咱們建立了GetWeatehrCommand:
package com.iamsevent.control.commands { import com.iamsevent.model.proxy.WeatherProxy; import org.puremvc.as3.interfaces.INotification; import org.puremvc.as3.patterns.command.SimpleCommand; /** * 該Command用以獲取天氣數據 * @author S_eVent * */ public class GetWeatehrCommand extends SimpleCommand { public function GetWeatehrCommand() { super(); } override public function execute(notification:INotification):void { //如果該Command存在對於多個notification的關聯,則須要使用switch...case語句來根據 //參數notification的名字執行不一樣邏輯。這裏因爲只存在惟一一個notification的關聯因此不用 weatherProxy.getWeather(notification.getBody() as String); } private function get weatherProxy():WeatherProxy { return facade.retrieveProxy(WeatherProxy.NAME) as WeatherProxy; } } }
該類經過咱們以前所提到過的facade.retrieveProxy方法獲取到了咱們用來作網絡交互工做的Proxy——WeatherProxy的引用,並在excute方法中把收到的notification(該notification名稱事實上就是NotificationDictionary.GET_WEATHER,稍後咱們會在ApplicationCommand中爲它和GetWeatehrCommand註冊起對應關係,若不註冊,則GetWeatehrCommand永遠也收不到該通知)中攜帶的數據,也就是咱們要獲取天氣預報的城市名傳遞給WeatherProxy的getWeather方法,WeatherProxy將會經過該方法來發送網絡請求,獲取對應數據。接下來讓咱們一塊兒來看看WeatherProxy類的代碼,看看它內部是如何工做的:
package com.iamsevent.model.proxy { import com.iamsevent.NotificationDictionary; import com.iamsevent.model.vo.ForcastVO; import com.iamsevent.view.global.Definition; import flash.events.Event; import flash.net.URLLoader; import flash.net.URLRequest; import flash.net.URLRequestMethod; import flash.net.URLVariables; import org.puremvc.as3.patterns.proxy.Proxy; /** * 天氣預報Proxy,負責獲取數據 * @author S_eVent * */ public class WeatherProxy extends Proxy { public static const NAME:String = "WeatherProxy"; private var _weatherXML:XML; private var _urlLoader:URLLoader = new URLLoader(); private var _urlRequest:URLRequest = new URLRequest(); private var _urlVar:URLVariables = new URLVariables(); private var _apiURL:String = "http://weather.yahooapis.com/forecastrss"; public function WeatherProxy() { super(NAME); } /** * 獲取某城市的天氣數據 * @param city 欲獲取數據的城市名(拼音) * */ public function getWeather(city:String):void { _urlVar.w = Definition.WOEID_MAP[city]; _urlVar.u = "c"; _urlRequest.url = _apiURL; _urlRequest.data = _urlVar; _urlRequest.method = URLRequestMethod.GET; _urlLoader.load(_urlRequest); _urlLoader.addEventListener(Event.COMPLETE, onBack); } private function onBack( e:Event ):void { _urlLoader.removeEventListener(Event.COMPLETE, onBack); _weatherXML = XML(_urlLoader.data); //將XML對象轉換成VO數組。數組中第一項是當前天氣狀況,後幾項是從今天開始4天的天氣狀況 var forcastList:Vector.<ForcastVO> = new Vector.<ForcastVO>(); //生成當前天氣數據。暫時只取用如下幾個標籤中的數據: //yweather:condition 當前天氣狀況 //yweather:forecast 將來兩天的天氣預報 // //以上標籤中的day和date屬性分別表明當天的星期及日期。low和high分別表明當天最低和最高溫度, //text表明天氣狀況,code表明天氣狀況對應圖片名,temp表明當前溫度 var vo:ForcastVO = new ForcastVO(); var yNameSpace:Namespace = _weatherXML.namespace("yweather"); var itemXML:XML = _weatherXML.channel.item[0]; var currentXML:XML = itemXML.yNameSpace::condition[0]; vo.condition = currentXML.@text; vo.temp = currentXML.@temp; vo.iconPath = currentXML.@code; vo.isCurrent = true; forcastList[0] = vo; //生成將來4天的天氣數據 for each(var elem:XML in itemXML.yNameSpace::forecast) { vo = new ForcastVO(); vo.day = elem.@day; vo.lowTemp = elem.@low; vo.highTemp = elem.@high; vo.iconPath = elem.@code; vo.condition = elem.@text; forcastList.push(vo); } sendNotification(NotificationDictionary.ON_GET_WEATHER, forcastList); } } }
對於通常Http交互來講,Flash客戶端使用URLRequest類就能夠實現目的。咱們目前須要獲取的天氣數據來自於雅虎天氣API,因而咱們將URLRequest的請求地址,即url屬性設置爲雅虎天氣API的地址,將參數設置爲雅虎天氣API可接受的參數(u表明溫度類型:f表示華氏溫度,c表示攝氏溫度;w表明欲獲取天氣預報的城市的世界地址編號(WOEID),若是你想獲取某個城市的WOEID,那麼你能夠在雅虎天氣首頁的城市搜索框中鍵入你要查詢WOEID的城市名,好比:Shanghai後,在出現的下拉選項中選擇正確的城市
以後,在打開的頁面中,你會發現該頁面的地址最後帶了一串數字,如http://weather.yahoo.com/china/shanghai/shanghai-12712465/。那麼這串數字:12712465就是咱們上海的WOEID了。咱們將正確的WOEID及溫度類型做爲參數放入一個URLVariables對象中,以後將該URLVariables對象做爲咱們URLRequest對象的data,最後調用load方法可以使請求發出。在Event.COMPLETE事件的偵聽函數中,咱們訪問URLRequest對象此時的data屬性能夠獲得HTTP請求的遠程返回數據。雅虎天氣API的返回數據是一個XML格式的數據,其中全部標籤的含義能夠訪問該API文檔獲知。對於本例來講,我只取了幾個標籤的數據,並將數據封裝成爲我視圖層WeatherPanel可用的數據格式後,將數據放入notification中派發出去。該通知將被視圖層WeatherPanel外嵌套的WeatherPanelMediator獲取並利用。至於具體怎麼顯示,不是我一個Proxy對象想管的事情了。
好了,有了Mediator、Command以及Proxy後,咱們差很少快大功告成了,最後在ApplicationCommand中,將咱們以前所寫的零件都配備、組裝起來:
public class ApplicationCommand extends SimpleCommand { private var _main:WeatherTest; …… override public function execute(note:INotification):void { //生成視圖 _main = note.getBody() as WeatherTest; var weatherPanel:WeatherPanel = new WeatherPanel(450, 300, 0xffffff, 1, "天氣預報", true); _main.addChild(weatherPanel); weatherPanel.x = (_main.stage.stageWidth - weatherPanel.width) / 2; weatherPanel.y = (_main.stage.stageHeight - weatherPanel.height) / 2; facade.registerMediator(new WeahterPanelMediator(weatherPanel)); //註冊Command facade.registerCommand(NotificationDictionary.GET_WEATHER, GetWeatehrCommand); //註冊Proxy facade.registerProxy( new WeatherProxy() ); } }
好了,這時如果你運行整個項目的代碼,應該可以看到一個比較完整的結果了,如果不想敲代碼,直接在文章頂部下載源碼即是。這是一個很是簡單的例子,卻涵蓋了PureMVC的使用方式,整個PureMVC的運做流程,我已經在以前用一張圖表示過了。在本次實踐下來,咱們能夠體會到PureMVC的哪些優點呢?我列出瞭如下幾點:
1.經過notification機制下降了整個框架的耦合度。Mediator想要獲取數據,直接發一個notification出去便可,老子只要數據,具體怎麼獲取的,老子無論!老子只要結果,不要過程!反正Mediator是最屌的,Command和Proxy都是打工仔,Command通常是做爲中介的存在,而Proxy通常只負責數據獲取、處理以及保管,如有須要,你能夠把某些Proxy作成單例,在整個項目中均可以隨時向它們獲取數據。如此明確的分工造就了PureMVC至關低的耦合度,以至於讓其很是適合團隊開發。每一個程序員只管本身模塊的功能實現,沒必要關心別的模塊中的代碼,每一個模塊間經過notification通信,很是簡單。好比程序員A對程序員B說:「喂,我這邊模塊上有一個按鈕,按下以後要打開你作的那個面板,我該怎麼辦?」「哦,你發個名字叫作XXX的notification就行了……」是的,你不用關心太多事情,在須要使用別人開發的功能時,每每在須要的時候發一個notification便可解決問題。
2.PureMVC是一個成熟的,而且被普遍使用的框架。正由於使用它的人多,因此你們基本上都懂得遊戲的規則,在一個公司招聘員工的時候,若新來的員工懂PureMVC的用法,他就能很快地看懂當前項目的源碼,並在其中能夠快速查找、修改。最重要的是,新來的這名員工在寫了新的代碼以後不會隨便亂放,他會根據現有的項目目錄結構來安排文件放置位置。要調用別人負責模塊的功能的時候也是簡單地發一條notification就能夠實現。總而言之,一個熟悉PureMVC的新員工在加入一個使用PureMVC框架進行開發的團隊中時可以很是快地融入進去,這其實也是那麼多公司在招聘啓事上寫「要求熟練掌握PureMVC」的緣由。
光說是沒有用的,列位道友仍是得靠多實踐,多看多用方能領悟其中之奧妙。結束語很少說了,列位中秋、國慶快樂吧!