従来のボタンの挙動をActionScript3.0で再現するためには、リスナー登録まわりでひと工夫する必要があります。
fladdict.net blog の【AS3メモ MouseEventについて】で言及されているように、マウスイベントはそのインスタンス上でしか発生しないため releaseOutside を実現するためには stage にリスナー登録する必要が出てきます。
また release と releaseOutside の判別にロールオーバー中かどうかのフラグで対応できるのと同様、rollOver と dragOver の判別や rollOut と dragOut の判別についてもマウス押下中かどうかのフラグで対応できます。
ところでマウスイベントがインスタンス上でしか発生しないという事実は、言い換えるとマウスカーソル下の全てのインスタンスに対して必ずマウスイベントが発生してしまうマウスカーソル下の最前面に配置されているインスタンスとそれを包括する全ての親インスタンスに対して一部を除く全てのマウスイベントが発生してしまうということです。
ボタンの releaseOutside を実現するためにボタン領域外で MouseEvent.MOUSE_UP を発生させると、そこがたまたま別のボタン上であった場合にそのボタンの release も同時に処理されてしまうことになります。
もっと単純に、ボタンをクリックするとそのボタンが配置されている Sprite をはじめ、ボタンのパスに含まれる全インスタンスにイベントが発生します。
もちろんこれは望ましくないのですが、簡単に制御することができます。
アドビ上条さんのブログエントリ【イベントフロー】に書かれているように、これらのイベントはキャプチャフェーズ→ターゲットフェーズ→バブリングフェーズの順で伝わります。
・・・正直な話、これだけでは意味が激しくわからないので、先ほどのボタンの話を例に挙げてみることにします。
ボタンのパスを仮に以下のように設定してみます(便宜上AS1.0/2.0表記)
_root.mc1.mc2.btn
また _root、mc1、mc2、btn の全てにマウスイベント MOUSE_DOWN を登録し、イベントが発生すると[インスタンス名:mousedown]とデバッグ出力するようにしておきます。
さて、ここで btn をクリックするとデバッグ出力には次のように表示されます
btn:mousedown
mc2:mousedown
mc1:mousedown
_root:mousedown
クリックしたインスタンスから階層を上へと辿りながらイベントが発生していることがわかります。
これはイベントフローのバブリングフェーズにあたりますが、addEventListener はこれがデフォルト設定となっています。
仮にこれを逆順、つまり _root から階層を下へと辿りながらイベントを発生させたいのであれば、addEventListener の第3引数を true にする必要がありますが、これはキャプチャフェーズでイベントを受け取るかどうかのフラグにあたります。
話を本題に戻しますが、btn をクリックした時にそのボタン以外にはイベントを受け取って欲しくないようにするためには、イベントフローを途中でカットします。
以下のスクリプトは btn 上でマウスが押下された時に呼ばれるイベントハンドラです。
private function onPressHandler( e:MouseEvent ):void {
trace("btn:down");
e.stopPropagation();
}
ここで stopPropagation() というメソッドが呼ばれていますが、これはイベントフローを中断するためのメソッドです。
これが呼ばれると、以降のインスタンスにはイベントは伝わらなくなりイベントハンドラが呼ばれなくなります。
また、releaseOutside を実現するにあたって他のボタンの release が発生しないようにしなければならないので、ボタンが押された時に
stage.addEventListener( MouseEvent.MOUSE_UP, onReleaseOutsideHandler );
と記述して、onReleaseOutisdeHandler で stopPropagation() を呼べばよいと考えてしまいがちですが、実はこれでは上手くいきません。
なぜなら、イベントフローのバブリングフェーズにおいて stage には最後にイベントが伝わるからです。
ここで思い出すのは一見使い道のなさそうな
キャプチャフェーズです。
キャプチャフェーズではバブリングフェーズとは逆に stage から順にイベントが伝わるので、ここでイベントフローの中断をかけると上手くいきそうです。
そこで上記のスクリプトを以下のように書き換えます。
stage.addEventListener( MouseEvent.MOUSE_UP, onReleaseOutsideHandler, true );
これでどのイベントハンドラよりも先にこの onReleaseOutisdeHandler が呼ばれるので、stopPropagation() により余計なイベントを抑えることができます。
おおよそ全てのボタンについてこの処理を埋めておく必要があるので、どう考えてもカスタムクラスを作るべきではないでしょうか。
というか作ってるんですが、Takaさんも作ってるわけで上手くすり合わせできればなーと。