AS3 事件框架- Signals篇

这篇文章详细的介绍了Robert Penner的AS3 Signals是什么,以及如何使用它让对象间的沟通更迅捷。它可以避免你使用常规的ActionScript事件机制,用到的代码量更少。
我们将通过范例来了解不同类型的signals,从而向大家描绘出如何在实际项目中应用signals。我希望你会和我一样,喜欢上AS3 Signals带来的对象沟通方便的感觉。其中一个裨益就是Signals非常容易掌握应用。你将很快实现它,它也会带来更多的便捷。那么我们开始吧!
如果你想亲历亲为,也是不错的主意,可以直接去到GitHub并下载AS3 Signals SWC文件,在你中意的IDE里创建项目(比如我会选择FDT)并导入SWC文件到你的项目中去。
什么是AS3 Signals呢?

Signals是一个AS3事件的新的机制,灵感来自于C#的事件和Qt中的signals / slots。(Robert Penner)
Robert Penner宣传Signals是“脱离事件独自思考”。这个口号用来描述Signals是一种轻量级并且是强制类型的AS3通信工具。
这已经说明了一些内幕,更详细的解释是:
Signals 是某种程度上可以替代AS3内置事件的框架。
它在API里整合了结合C#优异的signals思想和功能性的事件思想为一体。它比内置的事件更为迅捷,也少了很多呆板的代码。更难能可贵的是,它里面同样可以使用内置的事件,而且是内置事件运行速度的4倍。

什么是Signal? 

一个Signal其本质上是一个微型的针对某个事件的特定的派发者,附带它本身的监听者数组。
一个Signal的基本概念就是,不会使用类似内置事件那种方式、基于字符串的频道,而是化为一个类中具体的event/signal成员。这意味着我们这些开发者,在对象之间如何连接和沟通等方面,有了更多的控制。我们不再需要字符串变量来代表我们的Signals。Signals能够表达为真正的对象。
如果你已经是一个ActionScript开发者,有时候在你的项目里创建自定义的事件子类时就会相当痛苦。定义常量,定义事件等等。添加监听者,移除监听者,并总是在你的派发类里继承 EventDispatcher 。你将会发现Signals会在很短时间使这一切变得轻松自如。

Signal的基础
从最基本的Signal开始吧,比如我们用它来替代一个自定义AS3事件。Signal可以简单的视为一个对象。一个基本的Signals属于你的派发类的一个public属性。
首先能发现的好处就是我们不再像使用内置的事件体系一样必须扩展EventDispatcher类来实现派发类。
来看看一个简单的AlarmClock例子来理解语法。
[insideria.basicSignal.AlarmClock.as]

 package insideria.basicSignal 
{
         import org.osflash.signals.Signal;
 
         public class AlarmClock 
         {
                  public var alarm:Signal;
  
                  public function AlarmClock() 
                  {
                           alarm = new Signal();
                   }
                  
                  public function ring():void
                  {
                           alarm.dispatch();
                   }
          }
}

  使用AlarmClock,我们首先把它实例化。之后我们使用alarm这个public属性的add() 方法,也就是我们的Signal,来添加一个名为onRing的监听者。然后我们需要闹钟响起了,所以我们调用它的ring() 方法。就是这样轻松。如果你发布了程序,你将在控制台看到“Wake up !”的出现。
传递参数
基本的Signal的本领不止如此。我相信你迫切想知道我们如何使用Signal来传递参数。这也很简单,来让我们更新AlarmClock类,从而可以传递time给我们的WakeUp类。

[insideria.basicSignalArguments.AlarmClock.as]

package insideria.basicSignalArguments 
{
         import org.osflash.signals.Signal;
 
         public class AlarmClock 
         {
                  public var alarm:Signal;
  
                  public function AlarmClock() 
                  {
                           alarm = new Signal(String);
                   }
                  
                  public function ring():void
                  {
                           alarm.dispatch("9 AM");
                   }
          }
}

  So all we have to do to deliver a strongly-typed argument (you can also deliver untyped arguments) via our Signal is defining the type in the Signals constructor and dispatching whatever we want in the dispatch() method. You can pass multiple arguments of same or different type by separating them with a comma.

因为我们所作的改动就是通过我们的Signal传递了一个强制类型的参数(你也可以传递无类别的参数),它会在Signal的构造函数里声明,使用dispatch()来派发任何想要传递的参数。你也传递多个同类型或不同类型的参数,将它们用","分割开就行。

alarm = new Signal(String, Number, int, uint, Boolean);
alarm.dispatch("9 AM", 45.4, -10, 5, true);

  现在来看看我们的主程序接口如何trace出time吧。
[insideria.basicSignalArguments.WakeUp.as]

package insideria.basicSignalArguments
{
         import flash.display.Sprite;
 
         public class WakeUp extends Sprite
         {
                  private var alarmClock:AlarmClock;
  
                  public function WakeUp() 
                  {
                           alarmClock = new AlarmClock();
                           alarmClock.alarm.add(onRing);
                           alarmClock.ring();
                   }
  
                  private function onRing(time:String):void
                  {
                           trace("Wake up! It's already " + time);
                   }
          }
}

  Signal的监听者现在必须添加一个参数。否则编译器会抛出参数错误。为了清晰起见我们还称它为time。注意这个参数也要声明类别。现在我们运行程序,控制台会说“Wake up ! It's already 9 AM”. 

添加和移除Signals
在实际编程中你经常遇到一种情形:有个事件你只需要激活一次,然后要立即销毁。比起内置事件,Signals提供了一个轻松的方式来完成,而不需要写那一堆烦人的代码,比如like addEventListener(...), removeEventListener(...).

因此我们来修改下WakeUp,替换add()方法为addOnce(),让它只捕捉Signal一次。

alarmClock.alarm.addOnce(onRing);

  这样监听者将只会被执行一次,然后它会被自动的移除。如果使用的是add()方法,我们需要移除监听者,即可以使用remove(listener)方法,也可以使用Singnal的removeAll()方法,如果特定的Singnal附加多个监听对象的话。来看看它是什么样的:
alarmClock.alarm.remove(onRing);
alarmClock.alarm.removeAll();
目前为止我们已经了解了很多经常使用到的特性。我猜你也会好奇如何从Signal中获取更多的信息。比如在内置事件里,会告诉我们目标来源的一切。Signal也能做到,继续看下去。

DeluxeSignal和GenericEvent
DeluxeSignal要比我们惯用的内置ActionScript事件更近一步。DeluxeSignal能让我们访问目标,尤其重要的是,可以访问到Signal本身。所以我们可以操作二者。当然DeluxeSignal也拥有基本Signal的功能。现在来更新我们的AlarmClock,结合GenericEvent来使用DeluxeSignal。从这个过程中你会了解GenericEvent的。

[insideria.deluxeSignal.AlarmClock.as]

package insideria.deluxeSignal
{
         import org.osflash.signals.DeluxeSignal;
         import org.osflash.signals.events.GenericEvent;
 
         public class AlarmClock 
         {
                  public var alarm:DeluxeSignal;
                  public var message:String;
  
                  public function AlarmClock() 
                  {
                           alarm = new DeluxeSignal(this);
                           message = "This is a message from our AlarmClock";
                   }
                  
                  public function ring():void
                  {
                           alarm.dispatch(new GenericEvent());
                   }
          }
}

  我们创建了一个DeluxeSignal的公开属性,并在构造函数里实例化,传递了this到DeluxeSignal的构造函数里。这样DeluxeSignal会知道AlarmClock。我们还创建了一个message的公开属性,可以在之后被监听者访问到。
这是一个演示如何用GenericEvent来传递多个参数的例子。说到GenericEvent我们要看看ring()方法。DeluxeSignal的派发和之前我们使用的基本Signal一样,不过这次我们传递了一个GenericEvent的实例给它。这个GenericEvent将附带Signal的来源以及关于它本身的信息。让我们看看如何在主程序文件里访问这些信息吧。
[insideria.deluxeSignal.WakeUp.as]

package insideria.deluxeSignal 
{
         import org.osflash.signals.events.GenericEvent;
         import flash.display.Sprite;
 
         public class WakeUp extends Sprite
         {
                  private var alarmClock:AlarmClock;
  
                  public function WakeUp() 
                  {
                           alarmClock = new AlarmClock();
                           alarmClock.alarm.add(onRing);
                           alarmClock.ring();
                   }
  
                  private function onRing(event:GenericEvent):void
                  {
                           trace(event.target);
                           trace(event.signal);
                           trace(event.target.message);
                   }
          }
}

  只有监听者改动过了。监听者现在接收GenericEvent的参数,就是我们通过AlarmClock类来传递的那个。
要记住我们就是从这里获取信息的。现在我们可以trace下, 如同我们在内置事件里做的那样,可以获取到target,以及像是message等的其他属性。唯一特别的是我们也可以访问到AlarmClock类里派发事件的Signal对象。

我们已经学习了两种Signal。接下来了解下如何通过AS3 Signal来使用内置事件吧。
NativeSignals
最终AS3 Signals能作为一个完整的内置事件替代品是它可以让开发者访问到内置事件。Robert Penner对这项任务也非常小心在意的。
因为我总是喜欢给出一些实际的范例,因为我们先来看看NativeSignal是如何在运行时加载另一个SWF文件。
那我们第一件要做的事就是创建一个我们用来载入的SWF吧。

因为我们需要在下面的类中用到ADDED_TO_STAGE事件,首先关注下NativeSignal类的使用。
[insideria.nativeSignals.AddToStage.as]

package insideria.nativeSignals
{
         import org.osflash.signals.natives.NativeSignal;
 
         import flash.display.Sprite;
         import flash.events.Event;
 
         public class AddToStage extends Sprite 
         {
                  public function AddToStage()
                  {
                           var added:NativeSignal = new NativeSignal(this, Event.ADDED_TO_STAGE, Event);
                           added.addOnce(onAdded);
                   }
  
                  private function onAdded(event:Event):void 
                  {
                           graphics.beginFill(0xCCCCCC);
                           graphics.drawRect(0, 0, 100, 100);
                           graphics.endFill();
                   }
          }
}

  如你所见,我们创建了一个本地的NativeSignal,第一个传参为目标this,第二个参数是我们要使用的事件,第三个参数也是一个事件类。和我们之前讨论过的基础Signals一样,我们使用addOnce方法,仅仅执行Signals一次。当Signals被执行过一次后,我们不需要担心如何清除它。
sible to perhaps click on.
现在,就像我们真的在使用内置事件一样,我们创建了一个onAdded的回调函数。执行函数接收了一个类型为Event的事件参数。确保在执行函数里传递的参数和在NativeSignal实例中的事件类别一致。
在我们的onAdded回调函数里,我们只是绘制了一些随机图像来确保它确实被加载到其他文件中了。当然我们可以用NativeSignal来做更多的东西,比如有一些可视的物件能点击。
现在开始创建我们要用到的这个个SWF文件吧。

创建loading class
我们已经创建了一个可视化的,扩展自Sprite的类。下面我们需要3个private属性来含有我们的native Signals。别被这些private的属性迷惑住了————他们不会像基本的Signal那样需要在外部访问到。

private var loadedSignal:NativeSignal;
private var progressSignal:NativeSignal;
private var faultSignal:NativeSignal;
下面我们需要加载二进制文件的基础设置。因此我们创建了一个URLRequest,一个Loader,一个指向Signals的EventDispatcher。

var request:URLRequest = new URLRequest("AddToStage.swf");
var loader:Loader = new Loader();
var signalTarget:IEventDispatcher = loader.contentLoaderInfo;
现在我们将使用NativeSignal类来创建一个本地的Signal,服务于我们的加载事件,progress事件和默认事件。我们将保存我们的loader实例的contentLoaderInfo属性为一个IEventDispatcher的变量。

loadedSignal = new NativeSignal(signalTarget, Event.COMPLETE, Event);
loadedSignal.addOnce(onLoaded);
                        
progressSignal = new NativeSignal(signalTarget, ProgressEvent.PROGRESS, ProgressEvent);
progressSignal.add(onProgress);
                        
faultSignal = new NativeSignal(signalTarget, IOErrorEvent.IO_ERROR, IOErrorEvent);
faultSignal.addOnce(onFault);
创建这样的一个本地Signal对象的语法有如下几点要注意:第一个参数是目标,第二个是附带常量的事件,第三个仍旧是event类。另外一点是保持event handler里的参数类别和在native Signal对象里用到的一致。最后一点就是要注意确实已经开始加载行为了。

loader.load(request);

接下来我们需要设置处理Signals的方法。

private function onFault(event:IOErrorEvent):void 
{
         trace("Something went wrong!");
}

private function onProgress(event:ProgressEvent):void 
{
         trace((event.bytesLoaded / event.bytesTotal) * 100);
}

private function onLoaded(event:Event):void 
{
         progressSignal.removeAll();
         addChild(event.target.content);
}

  这样我们可以在加载出错时作出响应,我们可以在加载过程中显示进度,而且在完成后把内容添加进display list,也会把progress Signal移除,因为我们不再使用它了。如果你已经理解了使用add()和addOnce()的概念,很明显我们应该在fault和complete里使用addOnce,因为它们只需要执行一次。而add()应该用在progress里,因为它在加载完成之前,会执行很多次。
如果你运行程序,你会看到在左上角有一个100x100像素的方块。如果有哪里出了问题,控制台会提示“Something went wrong!”,因为我们已经硬编码到类里了。如果你遇到错误,请确保URLRequest实例里的链接路径确实指向了我们希望加载的SWF文件路径。

下面是已经完成的完整代码:

[insideria.nativeSignals.Loading.as]

package insideria.nativeSignals
{
         import org.osflash.signals.natives.NativeSignal;
 
         import flash.display.Loader;
         import flash.display.Sprite;
         import flash.events.Event;
         import flash.events.IEventDispatcher;
         import flash.events.IOErrorEvent;
         import flash.events.ProgressEvent;
         import flash.net.URLRequest;
 
         public class Loading extends Sprite 
         {
                  private var loadedSignal:NativeSignal;
                  private var progressSignal:NativeSignal;
                  private var faultSignal:NativeSignal;
  
                  public function Loading()
                  {
                           var request:URLRequest = new URLRequest("AddToStage.swf");
                           var loader:Loader = new Loader();
                           var signalTarget:IEventDispatcher = loader.contentLoaderInfo;
                           
                           loadedSignal = new NativeSignal(signalTarget, Event.COMPLETE, Event);
                           loadedSignal.addOnce(onLoaded);
                           
                           progressSignal = new NativeSignal(signalTarget, ProgressEvent.PROGRESS, ProgressEvent);
                           progressSignal.add(onProgress);
                           
                           faultSignal = new NativeSignal(signalTarget, IOErrorEvent.IO_ERROR, IOErrorEvent);
                           faultSignal.addOnce(onFault);
                           
                           loader.load(request);
                   }
  
                  private function onFault(event:IOErrorEvent):void 
                  {
                           trace("Something went wrong!");
                   }
  
                  private function onProgress(event:ProgressEvent):void 
                  {
                           trace((event.bytesLoaded / event.bytesTotal) * 100);
                   }
  
                  private function onLoaded(event:Event):void 
                  {
                           progressSignal.removeAll();
                           addChild(event.target.content);
                   }
          }
}

  处理MouseEvents

我会给出另外一个例子来说明,比如如何处理点击事件。返回AddToStage类,我们添加2个private属性。
private var clicked:NativeSignal;
private var box:Sprite;

现在在box里绘制一个随机图形,并把它添加到display list。

box = new Sprite();
box.graphics.beginFill(0xCCCCCC);
box.graphics.drawRect(0, 0, 100, 100);
box.graphics.endFill();
addChild(box);

最后添加native clicksignal和回调函数

clicked = new NativeSignal(box, MouseEvent.CLICK, MouseEvent);
clicked.add(onClicked);

private function onClicked(event:MouseEvent):void 
{
        trace("clicked");
}

发布一下这个文件,编译成SWF,然后运行那个loading SWF.当你点击box,你的控制台会在每次点击时显示“clicked”

现在我们不但通过AS3 Signals来加载外部文件,还注册了一个点击监听,并学习了如何使用ActionScript的内置事件。这种方式是广泛适用的。

结语

这篇文章你可以学到使用Robert Penner的AS3 Signal是如何容易。我们分别了解了3种Signals,以及如何使用它们。我希望这篇文章对你有所裨益。也希望你能喜欢这种交流方式。最后我也要分享一些资源给大家。

转自:http://bbs.9ria.com/thread-66669-1-1.html

 

posted @ 2012-12-19 16:03  itank  阅读(1412)  评论(0编辑  收藏  举报