用Asroute解决复杂状态切换问题
项目地址:https://github.com/boycy815/asroute
首先明确几个概念
状态:
很多情况下,一个复杂的UI组件可能会有很多种不同的“状态”,不同的“状态”下组件本身对外界会有不同的行为,外界根据组件的“状态”会对其做不同的操作。
状态节点:
组件的“状态”情况由一系列“状态节点”共同决定,每个“状态节点”都是一个只有两种值的布尔值。一般“状态节点”都会有一定意义,比如“在家”,“在公司”这是两个地理位置维度上的“状态节点”。如果“在家”这个“状态节点”被选中(值为true),那么表示当前正在家里。
子状态:
某个“状态节点”可能有更详细的描述,举个例子说明,如果“在家”算作一种“状态节点”,那么“在客厅”和“在厨房”应该算作“在家”的“子状态”。“子状态”被选中时,其父节点一定会先被选中。
互斥状态:
“互斥状态”也是个“状态节点”,他的“子状态”不能同时被选中,通常情况下这些“子状态”都是对同一个维度的事实进行描述,比如“在客厅”和“在家”都是在地理位置进行描述,一个人不可能同时在客厅和在厨房吧。
共存状态:
“共存状态”也是个“状态节点”,他的“子状态”允许同时被选中,通常他的“子状态”描述的都是不同维度的事实,比如“在客厅”和“睡觉”。
我们把上面介绍的几种状态节点组成树形状态,就能很好的描述当前系统处于什么样的状态。下面我们通过视频播放器举例说明
我们需要播放器有“close”,“inited”,“streaming”三种状态节点,其中close表示播放器未载入任何播放信息,inited表示已经载入播放信息随时可以准备开始播放,streaming表示已经连接,三种状态不能同时存在,所以是互斥的。
其中streaming状态带有“playing”和“buffering”两个子状态,分别从两个维度描述当前播放情况,属于共存状态。playing有play和pause是两个互斥状态,表示用户当前将播放器暂停或者播放;buffering有full和empty两个互斥状态,表示当前网络加载情况引起的状态。
这样的结构很好的描述了播放器的状态和各个状态节点之间的制约关系。下面我们通过asroute用代码把它描述出来。
var player:OrState = new OrState(); var close:State = new State(player); var inited:State = new State(player); var streaming:State = new State(player); var playing:OrState = new OrState(streaming); var play:State = new State(playing); var pause:State = new State(playing); var buffering:OrState = new OrState(streaming); var full:State = new State(buffering); var empty:State = new State(buffering);
OrState和State的构造函数参数为状态节点的父节点,一旦指定父级(或者不指定)就不能更改;OrState为State的子类,其子状态是互斥状态。
当某个状态被选中后发生事件的顺序:
当一个状态节点从未选中转变成选中时(值从false变为true),会发生StateEventConst.ENTER事件,从选中转变成未选中时(值从false变为true),会发生StateEventConst.EXIT事件。
如果一个状态节点在被选中的时候,其父级必定被选中。所以为了维持这个关系,如果一个状态节点即将被选中而其父级没有被选中,那么系统会自动先将其父级选中,这个规则会一直对其爷爷级祖宗级生效。另外如果对一个状态节点取消选中,那么在此之前其所有子状态也会先被取消选中。
OrState的特殊性在于,当其子状态被选中时,其他已被选中的子状态会先被取消选中。
可以通过下面的代码监听状态的改变
player.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("player enter") } ); player.addEventListener(StateEventConst.EXIT, function(e:Event):void { trace("player exit") } ); close.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("close enter") } ); close.addEventListener(StateEventConst.EXIT, function(e:Event):void { trace("close exit") } ); inited.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("inited enter") } ); inited.addEventListener(StateEventConst.EXIT, function(e:Event):void { trace("inited exit") } ); streaming.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("streaming enter") } ); streaming.addEventListener(StateEventConst.EXIT, function(e:Event):void { trace("streaming exit") } ); playing.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("playing enter") } ); playing.addEventListener(StateEventConst.EXIT, function(e:Event):void { trace("playing exit") } ); play.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("play enter") } ); play.addEventListener(StateEventConst.EXIT, function(e:Event):void { trace("play exit") } ); pause.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("pause enter") } ); pause.addEventListener(StateEventConst.EXIT, function(e:Event):void { trace("pause exit") } ); buffering.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("buffering enter") } ); buffering.addEventListener(StateEventConst.EXIT, function(e:Event):void { trace("buffering exit") } ); full.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("full enter") } ); full.addEventListener(StateEventConst.EXIT, function(e:Event):void { trace("full exit") } ); empty.addEventListener(StateEventConst.ENTER, function(e:Event):void { trace("empty enter") } );
控制状态节点改变:
每个状态节点都有个select方法,所以我们可以随意得选中我们想要的状态节点,系统会自动处理每个状态节点的关系,如
var ui:TextField = new TextField(); addChild(ui); ui.width = 800; ui.height = 600; ui.htmlText = '<a href="event:player">player</a>\n<a href="event:close">close</a>\n<a href="event:inited">inited</a>\n<a href="event:streaming">streaming</a>\n<a href="event:playing">playing</a>\n<a href="event:play">play</a>\n<a href="event:pause">pause</a>\n<a href="event:buffering">buffering</a>\n<a href="event:full">full</a>\n<a href="event:empty">empty</a>\n'; ui.addEventListener(TextEvent.LINK, onLink); function onLink(e:TextEvent):void { if (e.text == "player") { player.select(); } if (e.text == "close") { close.select(); } if (e.text == "inited") { inited.select(); } if (e.text == "streaming") { streaming.select(); } if (e.text == "playing") { playing.select(); } if (e.text == "play") { play.select(); } if (e.text == "pause") { pause.select(); } if (e.text == "buffering") { buffering.select(); } if (e.text == "full") { full.select(); } if (e.text == "empty") { empty.select(); } } //自定义状态机跳转测试 inited.addEventListener(StateEventConst.ENTER, function(e:Event):void { close.select() } ); streaming.addEventListener(StateEventConst.ENTER, function(e:Event):void { close.select() } ); play.addEventListener(StateEventConst.EXIT, function(e:Event):void { play.select() } );
但是有一点是要注意的,你不能直接让一个状态节点的值置为false,因为状态节点的值被置为false一定是因为别的互斥状态被选中而引起的。