StarlingMVC Framework中文教程
配置与开始
将Starling项目配置为StarlingMVC项目,仅需几行代码。在继承于starling.display.Sprite的起始类里,创建一个StarlingMVC的实例,并传递给它三个参数:Starling显示对象的根对象(起始类的实例)、StarlingMVCConfig的实例,以及一些Beans(数组或BeanProvider)。
package com.mygame.views { import com.creativebottle.starlingmvc.StarlingMVC; import com.creativebottle.starlingmvc.config.StarlingMVCConfig; import com.creativebottle.starlingmvc.views.ViewManager; import com.mygame.models.GameModel; import starling.core.Starling; import starling.display.Sprite; public class GameMain extends Sprite { private var starlingMVC:StarlingMVC; public function GameMain() { var config:StarlingMVCConfig = new StarlingMVCConfig(); config.eventPackages = ["com.mygame.events"]; config.viewPackages = ["com.mygame.views"]; var beans:Array = [new GameModel(), new ViewManager(this)]; starlingMVC = new StarlingMVC(this, config, beans); } } }
StarlingMVCConfig的实例告诉StarlingMVC它应该处理的事件包及视图包的位置。Beans数组仅仅是一组对象集合,可以包括任何类型的对象,框架会区别待之。(译者注:框架仅处理在视图包下的view对象的[ViewRemoved]标签,在处理[EventHandler]标签时,也仅处理事件包下的事件类型。)
在Flash Builder上的额外设置
使用Flash builder编译产品,AS编译器会严格剥离非标准的Metadata标签,除非另有指定。StarlingMVC的自定义标签对于实现强大的自动依赖注入特征是必须的,如果没有这些标签,StarlingMVC将毫无作用。
使编译器充许StarlingMVC的自定义标签非常简介,仅需在项目上添加几行额外的编译参数即可。
为添加编译参数,右键单击项目,选择”Properties“,然后选择”ActionScript Compiler“,在“Additional compiler arguments”后面追加:
-keep-as3-metadata+=EventHandler
-keep-as3-metadata+=Inject
-keep-as3-metadata+=Juggler
-keep-as3-metadata+=PostConstruct
-keep-as3-metadata+=ViewAdded
-keep-as3-metadata+=ViewRemoved
这样StarlingMVC便可以工作了。
Beans
一个Bean是一个提供给StarlingMVC管理的一个对象的实例。Bean们可以被注入,接收注入,还有接收事件等。在StarlingMVC的配置阶段,有多种方式可以设置Bean元素:
对象实例
var beans:Array = [new GameModel(), new ViewManager(this)];
Bean实例
var beans:Array = [new Bean(new GameModel()), new Bean(new ViewManager(this))];
以上提供Bean实例的设置方法相比之下并无高明之处,但是Bean的构造器的第二个参数可以传递一个id。只有提供了id,在依赖注入时才可以使用。另外,如果没有提供id,Bean将以class type作key在框架中存储,如果你有两个类型相同的Bean,必须提供id,否则后者将覆盖前者。例:
var beans:Array = [new Bean(new GameModel(),"gameModelEasy"),new Bean(new GameModel(),"gameModelHard"), new ViewManager(this)];
BeanProvider实例
BeanProvider是Bean的集合,与简单的Bean数组相同,在BeanProvider内的bean,可以是任何类型,包括BeanProvider。
package com.mygame.config { import com.creativebottle.starlingmvc.beans.BeanProvider; import com.mygame.assets.AssetModel; import com.mygame.models.AudioModel; import com.mygame.models.GameModel; public class Models extends BeanProvider { public function Models() { beans = [new GameModel(), new Bean(new AudioModel(),"audioModel"), new AssetModel()]; } } }
有了BeanProvider,便可以把它作为元素之一以数组的方式提供bean:
var beans:Array = [new Models(), new ViewManager(this)];
ProtoBeans
ProtoBean是在被注入才会被创建的Bean。一般的Bean要求提供一个类实例,ProtoBean要求提供一个类定义及一个id。
var beans:Array = [new ProtoBean(Character,"character"), new ViewManager(this)];
这里使用ProtoBean将充许StarlingMVC为你创建类的实例。它每一次被注入,均会实例化一个新的类的实例,在这种情况下,使用”Character”类而不是像一般的Bean那样使用一个单例。让StarlingMVC框架去创建一个类的实例,而不是使用”new Character()”创建的好处在于,前者可以被注入、监管事件等处理所有依赖注入事务。
依赖注入
依赖注入发生在所有Bean及所有Starling对象上。在公开属性或gettter/setter标以[Inject]无数据标签便是一个依赖项。一个注入可以这样定义:
package com.mygame.controllers { public class GameController { [Inject] public var gameModel:GameModel; public function GameController():void { } } }
或者以id注入,如果id在指定Bean时已经提供了的话:
package com.mygame.controllers { public class GameController { [Inject(source="gameModel")] public var gameModel:GameModel; public function GameController():void { } } }
在上面的例子中,如果GameModel是一个普通的Bean,框架会以在配置时创建的单例赋值它。如果它是一个Protobean,框架会创建一个实例再注入到变量中。
StarlingMVC同样支持注入Bean的属性。为了使用这个功能,源Bean必须有一个id(在配置时使用new Bean指定的id)。注入Bean的属性,在[Inject]内bean id加上“.”+属性的名称即可:
package com.mygame.controllers { public class GameController { [Inject(source="gameModel")] public var gameModel:GameModel; [Inject(source="userModel.currentUser")] public var currentUser:User; public function GameController():void { } } }
在上面的例子中,userModel的属性currentUser将会注入到我们的控制器的currentUser属性上。这个功能还支持递归,如果你想注入currentUser的firstName属性,你可以这样做:
.
绑定
注入操作同样支持简单的绑定机制,当源对象发生变化时,被注入的属性将会被自动更新。
package com.mygame.controllers { public class GameController { [Inject(source="gameModel")] public var gameModel:GameModel; [Inject(source="userModel.currentUser", bind="true")] public var currentUser:User; public function GameController():void { } } }
如上所示,在[Inject]标签中使用一个bind=”true”的参数实现绑定。userModel的currentUser属性变化之时,StarlingMVC将自动更新所有使用绑定的注入。此招同样适用于getter/setters方法,运用此法可使代码轻而易举地的实现属性的变化。
package com.mygame.controllers { public class GameController { [Inject(source="gameModel")] public var gameModel:GameModel; [Inject(source="userModel.currentUser", bind="true")] public function set currentUser(value:User):void { _currentUser = value; // Do something to update your UI with the new value } public function get currentUser():User { return _currentUser; } private var _currentUser:User; public function GameController():void { } } }
绑定与Starling juggler相连,这意味着它会在每次advanceTime()方法被调用的时候自动检查绑定的属性的变化。这不同于Flex提供的即时绑定。绑定应该谨慎使用,因为检查绑定的属性变化是一个不小的开销。
做为自动绑定属性的另外一种选择,StarlingMVC支持通过使之失效的方式绑定。这个方法相对于自动绑定十分有效,因为在当属性更新时它给了你更多的控制。这个功能可以通过一个被注入的Bindings类例和一个”auto”参数实现:
package com.mygame.controllers { public class GameController { [Inject(source="gameModel")] public var gameModel:GameModel; [Inject(source="userModel.currentUser", bind="true", auto="false")] public function set currentUser(value:User):void { _currentUser = value; // Do something to update your UI with the new value } public function get currentUser():User { return _currentUser; } private var _currentUser:User; public function GameController():void { } } } package com.mygame.models { public class UserModel { [Bindings] public var bindings:Bindings; public function set currentUser(value:User):void { if(value != _currentUser) { _currentUser = value; bindings.invalidate(this, "currentUser"); } } public function get currentUser():User { return _currentUser; } private var _currentUser:User; } }
事件
派发事件
StarlingMVC的事件通过两种渠道派发:1)StarlingMVC包含一个全局的starling.events.EventDispatcher的实例,派发事件的一个快速方法便是使用这个dispatcher。该dispatcher可以通过[Dispatcher]元数据标签被注入到任何一个Bean内。 2)显示对象可以使用自有的dispatchEvent()方法派发事件。该方法仅当事件bubble设置为true时有效。
处理事件
事件处理被指示为在bean的公开方法上添加[EventHandler(event="")]元数据标签。标签内事件的参数有两种写法:一为事件类型字符串
package com.mygame.controllers { public class GameController { [EventHandler(event="scoreChanged")] public function scoreChanged(event:ScoreEvent):void { } } }
另一个为事件定义字符串:
package com.mygame.controllers { public class GameController { [EventHandler(event="ScoreEvent.SCORE_CHANGED")] public function scoreChanged(event:ScoreEvent):void { } } }
使用第二种方式的好处,在于StarlingMVC会在事件初始化时检查事件类型是否真实存在,如果不存在,将抛出异常。另外还可以防止错误字。(译者注:这是StarlingMVC要求开发者提供的eventsPackage的原因。)
在上述两个例子中,处理函数必须接受一个派发的事件并处理。然而,[EventHandler]标签的第二个参数充许你指定事件的哪些属性将被传递给事件处理函数。例如:
package com.mygame.controllers { public class GameController { [EventHandler(event="ScoreEvent.SCORE_CHANGED", properties="user, newScore")] public function scoreChanged(user:User, newScore:int):void { } } }
如上所示,StarlingMVC没有传递一个event整体给事件函数,而只是传递事件的”user” 与 “newScore”属性。注意:类型必须匹配,否则将会报错。
视图中间件
视图中间件是保持你的视图类与控制它们的代码分离的一种好方法。视图中间件的设定与其它Bean一样。使用[ViewAdded]元数据标签将一个视图链接到一个视图中间件之上。当视图被添加进显示列表之后,StarlingMVC将会检查所有标以[ViewAdded]标签的方法,如果被标识的方法的view参数的类型与被添加进显示列表的视图类型相同,显示对象将被传递进方法。同理,[ViewRemoved]标签用于当视图被从显示列表移除时所要执行的函数的注入。
package com.mygame.mediators { public class GameMediator { private var view:Game; [ViewAdded] public function viewAdded(view:Game):void { this.view = view; } [ViewRemoved] public function viewRemoved(view:Game):void { this.view = null; } } }
Bean的生命周期
一般情况下,bean被设置了初始化。但是,在bean被创建之后,依赖注入与事件监听并没有立即可用。为了在当bean完成注入时得到一个通知,我们可以在一个公开方法上添加[PostConstruct]标签,这个方法会在注入完成后被自动调用。同样的,当一个bean被销毁时,我们可以在一个公开方法上标以[PreDestroy]标签。该功能在所有标准bean及显示对象上可用。
package com.mygame.controllers { public class GameController { [Inject] public var gameModel:GameModel; [PostConstruct] public function postConstruct():void { // set up code here } [PreDestroy] public function preDestroy():void { // tear down code here } [EventHandler(event="ScoreEvent.SCORE_CHANGED", properties="user, newScore")] public function scoreChanged(user:User, newScore:int):void { } } } package com.mygame.controllers { public class GameModel { [Bindings] public var bindings:Bindings; public function set score(value:int):void { if(value != _score) { _score = value; bindings.invalidate(this, "score"); } } public function get score():int { return _score; } private var _score:int; } }
手动添加、移除Bean
手动添加、移除bean是通过事件完成的,派发BeanEvent.ADD_BEAN事件用于添加、处理一个新的bean,派发BeanEvent.REMOVE_BEAN事件将bean从系统移除。
package com.mygame.view { public class Game { public var gamePresentationModel:GamePresentationModel; [PostConstruct] public function postConstruct():void { gamePresentationModel = new GamePresentationModel(); dispatchEvent(new BeanEvent(BeanEvent.ADD_BEAN, gamePresentationModel)); } [PreDestroy] public function preDestroy():void { dispatchEvent(new BeanEvent(BeanEvent.REMOVE_BEAN, gamePresentationModel)); gamePresentationModel = null; } } }
所上所示,我们为视图创建一个Model并且作为一个bean添加进StarlingMVC。Model将被作为一个bean处理,并马上获得注入与事件处理的优势。
Command模式
StarlingMVC包括对Command模式的支持。Command本质上是事件派发,执行,销毁时被创建的Bean。向框架添加command,需要在bean里添加Command类定义。
package com.mygame.views { import com.creativebottle.starlingmvc.StarlingMVC; import com.creativebottle.starlingmvc.config.StarlingMVCConfig; import com.creativebottle.starlingmvc.views.ViewManager; import com.mygame.models.GameModel; import starling.core.Starling; import starling.display.Sprite; public class GameMain extends Sprite { private var starlingMVC:StarlingMVC; public function GameMain() { var config:StarlingMVCConfig = new StarlingMVCConfig(); config.eventPackages = ["com.mygame.events"]; config.viewPackages = ["com.mygame.views"]; var beans:Array = [new GameModel(), new ViewManager(this), new Command(DoSomethingEvent.DO_SOMETHING, DoSomethingCommand)]; starlingMVC = new StarlingMVC(this, config, beans); } } }
这将映射事件到Command。command类可以像其它bean一样接收注入,但不能接收事件,事件也只在执行单个command的周期内存在。在command内将执行的方法被标为[Execute]标签。
package com.mygame.commands { import com.mygame.events.NavigationEvent; import com.mygame.models.BubbleModel; import starling.events.EventDispatcher; public class DoSomethingCommand { [Dispatcher] public var dispatcher:EventDispatcher; [Inject] public var bubbleModel:BubbleModel; [Execute] public function execute(event:DoSomethingEvent):void { trace("did something!"); } } }
所上所示,当DoSomethingEvent.DO_SOMETHING事件被派发时,StarlingMVC将创建一个DoSomethingCommand的实例,执行execute方法,然后销毁它。Command类可以在标签中包括一个runOnce的可选参数。
package com.mygame.views { import com.creativebottle.starlingmvc.StarlingMVC; import com.creativebottle.starlingmvc.config.StarlingMVCConfig; import com.creativebottle.starlingmvc.views.ViewManager; import com.mygame.models.GameModel; import starling.core.Starling; import starling.display.Sprite; public class GameMain extends Sprite { private var starlingMVC:StarlingMVC; public function GameMain() { var config:StarlingMVCConfig = new StarlingMVCConfig(); config.eventPackages = ["com.mygame.events"]; config.viewPackages = ["com.mygame.views"]; var beans:Array = [new GameModel(), new ViewManager(this), new Command(DoSomethingEvent.DO_SOMETHING, DoSomethingCommand, true)]; starlingMVC = new StarlingMVC(this, config, beans); } } }
在这个例子中,一个command bean以这样的语句被添加:new Command(DoSomethingEvent.DO_SOMETHING, DoSomethingCommand, true)。最后的参数true,标识该comand只被运行一次。所以,当DO_SOMETHING事件被派发时,DoSomethingCommand的实例被框架创建,执行,最后销毁,并从映射中移除。这个功能在初始化时非常有用(译者注:指执行一次)。
EventMap
EventMap is a utility class for creating and managing event listeners. Using EventMap exclusively to create listeners within your class makes cleanup very easy.
EventMap是一个创建、管理事件监听的工具类。使用EventMap创建的事件监听易于清理。
package com.mygame.mediators { import com.creativebottle.starlingmvc.events.EventMap; public class GameMediator { private var eventMap:EventMap = new EventMap(); [ViewAdded] public function viewAdded(view:Game):void { eventMap.addMap(view.playButton,TouchEvent.TOUCH, playButtonTouched); eventMap.addMap(view.instructionsButton,TouchEvent.TOUCH, instructionsTouched); } [ViewRemoved] public function viewRemoved(view:Game):void { event.removeAllMappedEvents(); } private function playButtonTouched(event:TouchEvent):void { } private function instructionsButtonTouched(event:TouchEvent):void { } } }
Juggler
Starling的juggler用于管理游戏内的所有动画。对于一个使用Juggler便利的类,必须实现IAnimatable接口、定义
方法。全局的juggler引用可以通过这个实例属性访问:Starling.juggler。并且,这个属性可以使用[Juggler]标签被直接注入到bean中。(译者注:StarlingMVC1.1源码有一个专门的Processor用于处理Juggler的注入)
package com.mygame.mediators { import com.creativebottle.starlingmvc.events.EventMap; public class GameMediator implements IAnimatable { [Juggler] public var juggler:Juggler; [ViewAdded] public function viewAdded(view:Game):void { juggler.add(this); } [ViewRemoved] public function viewRemoved(view:Game):void { juggler.remove(this); } public function advanceTime(time:Number):void { // do some animation logic } } }
ViewManager
ViewManager是一个工具类,通过它可以用来在舞台上添加删除视图。当我们创建ViewManager事例的时候,我们需要传递一个根显示对象。这样我们通过ViewManager可以在Starling的任意位置方便的添加可视化交互内容。
设置视图
调用setView将会移除已有的视图并且添加新的视图。ViewManager处理实例化视图,并将其添加到堆栈中。
package com.mygame.controllers { public class NavigationController { [Inject] public var viewManager:ViewManager; [EventHandler(event="NavigationEvent.NAVIGATE_TO_VIEW", properties="viewClass")] public function navigateToView(viewClass:Class):void { viewManager.setView(viewClass); } } }
添加视图
调用addView方法将在现有视图上添加一个新的视图。这里通过addView方法将GameHUD类型的视图添加到现有视图管理器中。
package com.mygame.views { public class Game { [Inject] public var viewManager:ViewManager; private var hud:GameHUD; [PostConstruct] public function postConstruct():void { hud = new GameHUD(); viewManager.addView(hud); } } }
移除视图
通过调用removeView可以删除堆栈中制定的视图。
移除所有视图
调用removeAll可以移除堆栈中存在的所有视图。当你调用setView()的时候这个方法会被自动调用。