Flex 2の最近のブログ記事

YaehaEffect は呪いの館のクラッシュシーンを再現するエフェクトです。貴方のビジネスアプリケーションに更なるイ゛ェアアアを…やっつけでごめんね!プロジェクトファイルのダウンロードはこちらです。Flex Builder 3 用プロジェクト

サンプルはこちらです。

PityuunEffect は東方Projectのクラッシュシーンを再現するエフェクトです。貴方のビジネスアプリケーションに更なるピチューンを。プロジェクトファイルのダウンロードはこちらからどうぞ(Flex Builder 3 Public Beta 3)

サンプルはこちらから。

IBM developerWorks ぽく

いつもの画面に飽きていませんか?Flex 2 で用意されているデフォルトのローディング画面をちょっと変えたい時は、mx.preloaders.IPreloaderDsiplay インターフェイスを実装する flash.display.Sprite クラスのサブクラスを作成します。ここでは画像を使った簡単なプリローダーを作ってみます。

カスタム プリローダのサンプル

完成目標

ダウンロードの進捗状況の表現には、開始時はグレースケールの画像を表示し、徐々にフルカラー化されていく方法で再現することにします。大事な点は、著作権を回避しつつ視覚に訴える事です。

image.png

用意するもの

  • xxnyann.png
    柿の種が降ってきてあわててミドリゾウリムシを持って逃げる防空頭巾姿の白い生き物の画像ファイル。グレースケールとフルカラーの二つを用意します。彦根市のキャラクターに激似ですが、それは柿の種とミドリムシのコラボが産んだ奇跡です。
  • ダミーのファイル。実際にダウンロードした時に、プリローダー画面を確認できるようにするだけですので、ファイルサイズが大きければ何でも良いです。

実装

普通にプロジェクトを作成します。ディレクトリ構成は次のようにしました。

project.png

base.png がグレースケール画像、overlay.png がフルカラー画像、dummy.mp3 がダミー ファイルとなっています。

source/preloader/OverlayPreloaderDisplay.as
package preloader
{
  import flash.display.Bitmap;
  import flash.display.BitmapData;
  import flash.display.Sprite;
  import flash.events.Event;
  import flash.events.ProgressEvent;
  import flash.events.TimerEvent;
  import flash.geom.Point;
  import flash.geom.Rectangle;
  import flash.utils.Timer;
  import flash.utils.getTimer;
  
  import mx.preloaders.IPreloaderDisplay;
  import mx.preloaders.Preloader;

  /**
   * ダウンロードの進行状況を表示します。
   */
  public class OverlayPreloaderDisplay extends Sprite implements IPreloaderDisplay
  {
    /**
     * 表示領域の横幅。
     */
    public static const CANVAS_WIDTH:Number = 300;

    /**
     * 表示領域の縦幅。
     */
    public static const CANVAS_HEIGHT:Number = 200;
    
    /**
     * フェードアウト時間(ms)。
     */
    public static const CLOSE_EFFECT_DELAY:Number = 400;
    
    // 背景画像。
    [Embed(source="../../assets/base.png")]
    private var _baseImageClass:Class;
    private var _baseBitmap:Bitmap;

    // 上書き画像。
    [Embed(source="../../assets/overlay.png")]
    private var _overlayImageClass:Class;
    private var _overlayBitmap:Bitmap;
    
    private var _canvas:Bitmap;
    
    /**
     * OverlayPreloaderDisplay が Preloader の子として追加されると、Preloader によって呼び出されます。
     * これが、ダウンロードプログレスバー設定の開始点となります。
     */
    public function initialize():void
    {
      this._baseBitmap = new this._baseImageClass();
      this._overlayBitmap = new this._overlayImageClass();

      var canvasBitmapData:BitmapData = new BitmapData(CANVAS_WIDTH, CANVAS_HEIGHT);
      this._canvas = new Bitmap(canvasBitmapData);
      this.addChild(this._canvas);
    }
    
    /**
     * 表示画面を更新します。
     */
    protected function updateDisplay():void
    {
      var canvasBitmapData:BitmapData = this._canvas.bitmapData;

      // 進捗率
      var progress:Number = this.getProgress();
      var bounds:Number = Math.floor(canvasBitmapData.height * (1 - progress));

      // 背景描画
      var baseRect:Rectangle = new Rectangle();
      baseRect.left = 0;
      baseRect.top = 0;
      baseRect.right = this._baseBitmap.bitmapData.width;
      baseRect.height = bounds;

      var basePoint:Point = new Point(0, 0);

      canvasBitmapData.copyPixels(this._baseBitmap.bitmapData, baseRect, basePoint);

      // 上書き描画
      var olRect:Rectangle = new Rectangle();
      olRect.left = 0;
      olRect.top = bounds;
      olRect.right = this._baseBitmap.bitmapData.width;
      olRect.height = this._baseBitmap.bitmapData.height - bounds;

      var olPoint:Point = new Point(0, olRect.top);
      
      canvasBitmapData.copyPixels(this._overlayBitmap.bitmapData, olRect, olPoint);
      
      // 位置調整
      this._canvas.x = (this._stageWidth - this._canvas.width) / 2;
      this._canvas.y = (this._stageHeight - this._canvas.height) / 2;
    }
    
    private var _loadCompletedSize:uint = 0;
    private var _loadTotalSize:uint = 0;
    
    /**
     * 現在のダウンロード進捗状況の割合を数値で取得します。
     * 
     * @return 進捗開始持は 0、進捗が進むつれ最大 1 まで増えます。
     */
    private function getProgress():Number
    {
      if (isNaN(this._loadTotalSize) || this._loadTotalSize == 0)
      {
        return 0;
      }
      else
      {
        return this._loadCompletedSize / this._loadTotalSize;
      }
    }
    
    private var _closeEffectTimer:Timer;
    private var _closeStartTime:int;
    
    /**
     * 進捗画面を閉じる準備をします。
     */
    private function beginClose():void
    {
      this._closeStartTime = flash.utils.getTimer();
      this._closeEffectTimer = new Timer(30);
      this._closeEffectTimer.addEventListener(TimerEvent.TIMER, this.closeEffectTimer_timer);
      this._closeEffectTimer.start();
    }
    
    /**
     * 進捗画面を閉じます。
     */
    private function endClose():void
    {
      // 進捗画面を消すよう Preloader にお知らせ。
      this.dispatchEvent(new Event(Event.COMPLETE));
    }
    
    /**
     * 進捗画面を閉じるエフェクトを更新するたびに呼び出されます。
     */
    private function closeEffectTimer_timer(event:TimerEvent):void
    {
      var elapsed:int = flash.utils.getTimer() - this._closeStartTime;
      var alpha:Number = 1 - Math.min(1, elapsed / CLOSE_EFFECT_DELAY);
      
      this.alpha = alpha;

      if (alpha == 0)
      {
        this._closeEffectTimer.stop();
        this.endClose();
      }
    }
    
    /**
     * プリローダの進捗が進むたびに呼び出されます。
     */
    protected function preloader_progressHandler(event:ProgressEvent):void
    {
      this._loadCompletedSize = event.bytesLoaded;
      this._loadTotalSize = event.bytesTotal;
      
      this.updateDisplay();
    }
    
    /**
     * プリローダの進捗が完了した時に呼び出されます。
     */
    protected function preloader_completeHandler(event:Event):void
    {
      this.beginClose();
    }
    
    // 以下、下駄&雪駄
    
    private var _preloader:Preloader;
    
    public function set preloader(value:Sprite):void
    {
      this._preloader = Preloader(value);
      
      this._preloader.addEventListener(ProgressEvent.PROGRESS, this.preloader_progressHandler);
      this._preloader.addEventListener(Event.COMPLETE, this.preloader_completeHandler);
    }
    
    private var _stageHeight:Number;
    
    public function get stageHeight():Number
    {
      return this._stageHeight;
    }
    
    public function set stageHeight(value:Number):void
    {
      this._stageHeight = value;
    }
    
    private var _stageWidth:Number;
    
    public function get stageWidth():Number
    {
      return this._stageWidth;
    }
    
    public function set stageWidth(value:Number):void
    {
      this._stageWidth = value;
    }
    
    public function get backgroundImage():Object
    {
      // 手抜き
      return null;
    }
    
    public function set backgroundImage(value:Object):void
    {
      // 手抜き
    }
    
    public function get backgroundSize():String
    {
      // 手抜き
      return null;
    }
    
    public function set backgroundSize(value:String):void
    {
      // 手抜き
    }
    
    public function get backgroundAlpha():Number
    {
      // 手抜き
      return 0;
    }
    
    public function set backgroundAlpha(value:Number):void
    {
      // 手抜き
    }
    
    public function get backgroundColor():uint
    {
      // 手抜き
      return 0;
    }
    
    public function set backgroundColor(value:uint):void
    {
      // 手抜き
    }
  }
}
source/MyCustomPreloader.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" preloader="preloader.OverlayPreloaderDisplay" layout="vertical" viewSourceURL="srcview/index.html">
  <mx:Script>
    <![CDATA[
      [Embed(source="../assets/dummy.mp3")]
      private var sound:Class;
    ]]>
  </mx:Script>

  <mx:Style>
    Application
    {
      vertical-align: middle;
      background-gradient-colors: #ffffff, #ffffff;
    }
    
    Label
    {
      font-size: 40px;
    }
  </mx:Style>

  <mx:Label text="COMPLETED"/>
</mx:Application>

サンプル

出来上がった物はこちらです。サンプル

まとめ

  • カスタム プログレスバー クラスを作る。
    class MyPreloaderDisplay extends flash.display.Sprite implements mx.preloaders.IPreloaderDisplay
  • 手抜きをしたかったら mx.preloaders.DownloadProgressBar を継承(ActionScript 3 ならではの手軽さ)。
  • アプリケーション ルートの MXML で使用するカスタム プログレスバー クラスを指定。
    <mx:Application preloader="クラスパス">
  • ロードが終わったらカスタム プログレスバーが disptchEvent(new Event(Event.COMPLETE)) を発行する。そうでないといつまでもローディング画面が消えない。
  • Preloader が発行するイベントの詳細がないような…。以下発生するイベント。
    • Event.COMPLETE
    • FlexEvent.INIT_COMPLETE
    • FlexEvent.INIT_PROGRESS
    • FlexEvent.PRELOADER_DONE
    • ProgressEvent.PROGRESS
    • RSLEvent.RSL_COMPLETE
    • RSLEvent.RSL_ERROR
    • RSLEvent.RSL_PROGRESS

価格.comプロバイダ検索 API を使ったサンプル

こちらで公開してます。

ss.png

ライブラリのソースが見れちゃってステキ

Flex SDK 2 はソースが公開されているので問題の解決が早い。Flex ドキュメンテーション で公開されている分だけでは分かりにくい部分を確認する事ができます。

例えば mx.containers.TitleWindow オブジェクトを mx.managers.PopUpManager.addPopUp メソッドでモーダル表示させると TitleWindow.showEffect に設定したエフェクトは再生されません。TitleWindow.showEffect は FlexEvent.SHOW イベントをトリガにして再生されますが、PopUpManager が TitleWindow から SHOW イベントが発生しないようにしている為です。(PopUpManager はポップアップ ウィンドウの IUIComponent.setVisible メソッドを使って IUIComponent.visible 値を更新しています。でも、何故かしら?)。なのでポップアップ ウィンドウ側の setVisible メソッドをオーバーライドしてエフェクトを再生するようにしてしまえばいい…のか分かりませんが、とにかくその場しのぎが出来ます。ありがとうオープンソース。

flash.external.ExternalInterface クラスを使用する

flash.external.ExternalInterface は Flash Player とそのコンテナ(ブラウザなど)間で通信する為のクラス。ブラウザの HTML ドキュメントに Flash Player からちょっかいだしたり出されたりできる。

使い道

例えばシステムのフォントを列挙して JavaScript で利用する。サンプルはこちらscreenshot.png

使用した MXML ファイル

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" applicationComplete="this.applicationCompleteEventHandler(event);" layout="absolute" width="400" height="200" fontSize="12">
    <mx:Script>
        <![CDATA[
            import flash.external.ExternalInterface;
            import mx.events.FlexEvent;

            protected function applicationCompleteEventHandler(event:FlexEvent):void
            {
                var available:Boolean = ExternalInterface.available;
                this.availableLabel.text = "外部インターフェイス: "
                    + (available ? "利用可能" : "利用不可");

                if (available)
                {
                    ExternalInterface.addCallback("enumerateFont", this.enumerateFont);
                }
            }
            
            public function enumerateFont():Array
            {
                if (ExternalInterface.available)
                {
                    var fontNames:Array = new Array();
                    var fonts:Array = Font.enumerateFonts(true);
                    
                    for each (var font:Font in fonts)
                    {
                        fontNames.push(font.fontName);
                    }

                    return fontNames;
                }
                
                return null;
            }
        ]]>
    </mx:Script>
    
    <mx:Panel layout="vertical" title="フォント列挙" x="10" y="10" horizontalAlign="center" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10">
        <mx:Label id="availableLabel"/>
    </mx:Panel>
</mx:Application>

イベントの流れ

| | コメント(0)

イベントの発生順序

Flex ではイベントは DOM Level3 Event Model に沿った実装になっており、イベントの流れを3つの段階に分けて考えられます。発生順序もこの通り。

  1. キャプチャ段階
  2. ターゲット段階
  3. バブリング段階

それぞれの段階の違い

Flex アプリケーションでは全ての視覚オブジェクト (DisplayObject) は木構造になっています。イベントは親要素から子要素、またはその逆を辿り伝わっていきます。

キャプチャ段階
ルート要素から子要素へイベントが発生していく。イベント発生要素に辿り付くまでの段階
ターゲット段階
イベント発生要素でイベントが発生した段階。
バブリング段階
イベント発生要素の親要素からルート要素へ、キャプチャ段階の逆の順序で発生して行く。

tree.jpg

このアプリケーションの Button オブジェクトで click イベントが発生した場合、イベントは次のように発生します。

  1. キャプチャ段階
    1. Application
    2. Panel
    3. TitleWindow
    4. Accordion
    5. Canvas
  2. ターゲット段階
    1. Button
  3. キャプチャ段階
    1. Canvas
    2. Accordion
    3. TitleWindow
    4. Panel
    5. Application

実例

Panel コンテナ内をクリックすると、イベントを発生順に表示します。(要 Flash Player 9 以降)

使用した MXML コード

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="this.creationCompleteEventHandler(event);" layout="absolute" >
  <mx:Script>
    <![CDATA[
      import mx.collections.ArrayCollection;

      private var eventCount:int;
      private var eventPhaseTextTable:Object;
      
      protected function creationCompleteEventHandler(event:Event):void
      {
        // イベント ハンドラを登録する。
        this.panel.addEventListener(MouseEvent.CLICK, this.clickEventHandler);
        this.titleWindow.addEventListener(MouseEvent.CLICK, this.clickEventHandler);
        this.accordion.addEventListener(MouseEvent.CLICK, this.clickEventHandler);
        this.canvas.addEventListener(MouseEvent.CLICK, this.clickEventHandler);
        this.button.addEventListener(MouseEvent.CLICK, this.clickEventHandler);

        // キャプチャ段階のイベント ハンドラを登録する。
        this.panel.addEventListener(MouseEvent.CLICK, this.clickEventHandler, true);
        this.titleWindow.addEventListener(MouseEvent.CLICK, this.clickEventHandler, true);
        this.accordion.addEventListener(MouseEvent.CLICK, this.clickEventHandler, true);
        this.canvas.addEventListener(MouseEvent.CLICK, this.clickEventHandler, true);
        this.button.addEventListener(MouseEvent.CLICK, this.clickEventHandler, true);

        // イベントの結果表示を初期化する為のハンドラ。
        this.panel.addEventListener(MouseEvent.MOUSE_DOWN, this.mouseDownEventHandler);
        
        // イベント フェーズの文字列変換テーブル。
        this.eventPhaseTextTable = {
          (EventPhase.AT_TARGET.toString()) : "Target",
          (EventPhase.BUBBLING_PHASE.toString()) : "Bubbling",
          (EventPhase.CAPTURING_PHASE.toString()) : "Capturing"
        };
      }
      
      protected function mouseDownEventHandler(event:MouseEvent):void
      {
        this.eventCount = 1;
        this.dataGrid.dataProvider = new ArrayCollection();
      }
      
      protected function clickEventHandler(event:Event):void
      {
        var dataProvider:ArrayCollection = this.dataGrid.dataProvider as ArrayCollection;
        var item:Object = {
            "no" : this.eventCount,
            "target" : event.currentTarget.className,
            "phase" : this.eventPhaseTextTable[event.eventPhase]
        };
        dataProvider.addItem(item);
        this.eventCount++;
      }
    ]]>
  </mx:Script>

  <mx:Panel layout="absolute" title="Panel" top="10" left="10" right="10" id="panel" height="182">
    <mx:TitleWindow layout="absolute" left="10" top="10" bottom="10" right="10" showCloseButton="true" title="TitleWindow" id="titleWindow">
      <mx:Accordion id="accordion" left="10" top="10" bottom="10" right="10">
        <mx:Canvas label="Canvas" width="100%" height="100%" id="canvas">
          <mx:Button x="10" y="10" label="Click this" id="button"/>
        </mx:Canvas>

      </mx:Accordion>
    </mx:TitleWindow>
  </mx:Panel>
  <mx:Panel layout="absolute" left="10" top="200" bottom="10" right="10" title="Events">
    <mx:DataGrid id="dataGrid" top="10" left="10" right="10" bottom="10">
      <mx:columns>

        <mx:DataGridColumn width="50" dataField="no" headerText="No"/>
        <mx:DataGridColumn dataField="target" headerText="Target"/>
        <mx:DataGridColumn dataField="phase" headerText="Phase"/>
      </mx:columns>
    </mx:DataGrid>
  </mx:Panel>

</mx:Application>

このアーカイブについて

このページには、過去に書かれたブログ記事のうちFlex 2カテゴリに属しているものが含まれています。

前のカテゴリはFlashです。

次のカテゴリはUnicodeです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 4.12