需求
在一个小型客户端项目中,为了美化界面,除了网络连接、数据逻辑处理部分用VC实现以外,显示部分都使用Flash,速度要求不高,用Flash的好处是可以做出一些效果,而且美工可以只负责美工,程序员编写一些简单的Flash Action配合一下即可。
这是一个简单的Flash应用,半天时间就已经完成了。进入测试,通过所有要求。
随之而来的是版本升级问题,最初的简单版本经过3次升级,加上美工参与代码维护以后,基本上原来的代码已经散乱不堪,BUG开始涌现,维护问题突显。
原本打算抽调一名程序员协助美工完成脚本代码维护,不过最终没安排过来。美工自己显然已经不知道自己把代码改成什么样了,也找不出BUG出在什么地方。
程序工作的流程很简,从网络接收的数据经过简单分析处理,最终被分解为简单的“action”(实际上我们把它称为消息),传递给Flash,由它解释成显示数据。初期因为每个action只驱动一个显示动作,所以工作得很好,代码也很整齐。后来升级以后,美工介入编码,由于没有系统结构意识,在“一个Action驱动多个显示动作”时采用了愚蠢的“补贴”方式,致使每个处理action的函数都臃肿不堪,最终导致了蜘蛛网似的代码结构,全局变量(Flash中的绝对路径)到处使用,一个小的改动都可能使显示全部错误。
我开始思考解决办法。
美工的编码方式很简单但也不能说错误,他们有一套自己的编码方式,在短时间内强行去改变他们是没有意义的,让程序员参与编码不但浪费人力,反而让他们双方都觉得进度缓慢,唯一可行的方式是顺着美工的方向走。大致看了一下他们的代码,从网上抄过来的特效代码比较多,只是结构稍差,但并非不可行。
我首先要求美工:每一个小的显示部件单独开发,在一个独立的文件中做好、测试好,并且要保证在各个路径层次上执行正常(减少他们使用全局路径,最终确定了只使用一个全局路径以方便维护)。他们做这些没有任何问题,完成速度也很快。
然后我把这些小部分要用到的action分析了一下,基本上还是我们原来那些action,现在的要求是把这些action送达这些小部件中以进行处理。
问题明确了:每个action由数个不同的小部件侦听,只要写好这个类,并由这些部件注册侦听器即可。
功能描述
因为是在VC中发送这些action,为了在Flash脚本中处理简单一些,我们在VC中驱动Flash显示的代码统一形式为:
m_wndFlash.SetVariable (“_root.action_processor.action“, “action_type:action_body“);
第一个参数是action处理器组件的一个setter函数,后面的字符串就是传递给它处理的。因为在VC中无法直接调用组件的函数,只能用这种方式间接调用。根据这个约定,我在原来的基础上编写了ActionRegistry类,为了美工调用方便,做成了一个可视组件,实际上并不负责显示,只是方便他把组件拖到场景中并取个名字。
实现
实现过程并没有太复杂,所以我直接把代码发上来了。
class biz.blueskytech.ActionRegistry
{
private var _action_listenerlist_map : Object;
function ActionRegistry ()
{
_action_listenerlist_map = {};
}
function registryListener (type:String, obj:Object) : Void
{
if (_action_listenerlist_map[type] == undefined)
_action_listenerlist_map[type] = new Array ();
_action_listenerlist_map[type].push (obj);
}
function removeListener (type:String, obj:Object) : Void
{
var arr = _action_listenerlist_map[type];
if (arr != undefined)
{
for (var i=0; i<arr.length; i++)
if (arr[i] == obj)
arr.splice (i, 1);
}
}
function dispatchAction (type:String, args:Array) : Void
{
var arr = _action_listenerlist_map[type];
if (arr != undefined)
{
for (var i=0; i<arr.length; i++)
{
var obj = arr[i];
var funcName = "on" + type.charAt(0).toUpperCase () + type.substr (1);
if (typeof (obj[funcName]) == "function")
obj[funcName].apply (obj, args);
else
trace ("the listener not implement "+type+" handle");
}
}
else
{
trace ("action "+type+" no listeners");
}
}
function set action (str:String) : Void
{
if (str != undefined)
{
var delimiter_pos = str.indexOf (":");
if (delimiter_pos > 0)
{
var action_type = str.substr (0, delimiter_pos);
var action_body = str.substr (delimiter_pos+1);
if (action_type.length > 0)
{
//trace ("call listeners handler of action {"+action_type+"}");
var args = parse_body (action_type, action_body);
dispatchAction (action_type, args);
}
else
{
trace ("invalid action type or args");
}
}
else
{
trace ("invalid action format");
}
}
else
{
trace ("invalid action call");
}
}
function parse_body (type:String, body:String) : Array
{
var funcName = type+"Handler";
if (typeof (this[funcName]) == "function")
return this[funcName].apply (this, [body]);
else
{
trace ("action handler not found");
return [];
}
}
}
至于怎么生成组件,不是本文的重点,只要用一个空的MovieClip和它链接,并定义组件即可,是否导出编译组件并不重要。
调用实例
新建一个Flash文件,在里面加入一个ActionRegistry组件实例,命名为action_registry。
在第一桢加入下面的代码:
action_registry.testHandler = function (t:String):Array
{
return [5,4];
}
以上代码是为action_registry加入test消息的解析函数,用它来把消息解析成参数,供侦听回调函数使用。下面代码注册了2个侦听器,它们有不同的处理函数:
var obj = new Object ();
obj.onTest = function (t1:Number, t2:Number)
{
trace ("T1: "+t1);
trace ("T2: "+t2);
}
action_registry.registryListener ("test", obj);
var obj1 = new Object ();
obj.onTest = function (t1:Number, t2:Number)
{
trace (“Result: “+(t1+t2));
}
action_registry.registryListener ("test", obj1);
初始化就完成了。我们现在加入一个按钮,给它的click(或是release)事件编写代码:
on (click)
{
_root.action_registry.action = "test:0";
}
因为test消息的参数我们并没有用到,实际上从引号后面就可以空差。现在测试影片吧,你应该可以看到2个消息处理函数都执行了,这也达到了本文的要求。
如果你知道怎么在VC中给影片中的变量赋值,你自然会明白这个组件给你带来了什么——一个多路消息分派器。
如果你只是在Flash中使用此分派器,则无需先将消息格式化为字符串,可以直接调用:
_root.action_registry.dispatchAction (”test”, [5,4]);
这样做也提高了效率。
测试总结
现在我们只要在要侦听这个消息的地方去注册侦听器即可,对代码层次、调用顺序没有任何影响,很好地达到了要求。
本文也使用了一些Flash V2组件脚本的新语法。