zenith: 2007年4月アーカイブ

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

問題

プロジェクトの共有にて、リポジトリー・タイプを選択する時に SVN をアクティブにすると "JVM terminated Exit code=1" とエラー メッセージが出て Eclipse が終了してしまう。

環境

サーバ側は Subversion 1.4.3、Apache 2.2.4 + mod_dav_svn。クライアント側は Windows XP SP 2、Eclipse 3.2.2 + Subclipse 1.2.0 プラグイン。WebDAV でアクセス。

対処

Eclipse の設定から [チーム] > [SVN] > [SVNインターフェイス] を、JavaHL から SVNKit へ変更する事で解消。

原因

Eclipse のインストール フォルダに作成されるログでは org.tigris.subversion.javahl.SVNClient.status メソッドの呼び出し前後でこけていた。SVNKit にすると問題なしのように見える。そもそも JavaHL と SVNKit の違いは?JavaHL は Subversion の DLL を利用するのに対して、SVNKit はすべて Java で書き上げらている点、との事。うーん、どこか古いファイルを参照してしまってるのかも…。SVNKit ではプロトコルに file:// が使用できないだけで、いい加減に使う分には問題なさそう。とりあえずこれでよし。

LEGO Digital Designer

| | コメント(3)

子供の頃欲しかったあのレゴブロックで遊べる!

どうして『あの』を付けたがるんだろう、LEGO Digital Designer がとても面白い。

all.jpg
作品名『遮断機のある風景』

neko-guruma.jpg
2003年の東京モーターショーで注目された猫車。V8エンジン搭載し、4WD(Four-foot Wheel Drive の略で四足駆動の意味)でどんな悪路も走破してしまうのが売り。線路がむき出しの踏み切りだって乗り越えられちゃう。隣の隣の家の猫がデザイン モチーフ。

driver.jpg
運転席からの眺めは最高だ!大きなネコミミがポイント。前方視界がゼロな分じっくり観賞できるぞ。よく見たらハンドルに手が届いてない。設計図を持たせてみたらとても自然体になり、思わぬ効果が得られた。

driver-face.jpg
とてもいい笑顔のドライバー。このLDDに収録されているフェイスは、写真に写っている口ヒゲの生えた親父とあごヒゲが生えている親父の二つだけ。思い切ったセレクトであると言える。

tekogi.jpg
「とっつぁんしっかりしな~」「貴様ぁ~日本に帰ったら憶えてろぉ!」ルパン三世でよく見かけるシチュエーションを忠実に再現。手漕ぎ感がスローライフを演出させるとして団塊の世代に大好評。一畑電鉄(※)の新型車両に採用される話が進んでいる。

※日本一料金が高い私鉄として有名。zenith の生まれ故郷で唯一の大量輸送手段である。昨今土砂崩れや少子化により廃線の危機が叫ばれている。山陰に来たらその揺れ具合を一度は体験していただきたい。

猫バトン

| | コメント(8)

にゃーんだと?

語尾にニャとかふざけてるのか!そんなバカなこと出来な…な…

猫化ブックマークレット

ブックマークレットと呼ばれてるやり方で実装しました。利用方法は簡単。

  1. 変換したいページを呼び出します。
    how-step-1.png
  2. ブラウザのアドレス欄に javascript:(function(){var u="http://zenith.sakura.ne.jp/bookmarklet/arisyu.js" ;var d=document;var s=d.createElement('script');s.charset="UTF-8";s.src=u;d.body.appendChild(s);})() をコピーして貼り付け、 Enter キーを押します。するとあら、こんなところにニャンコが…!
    how-step-2.png

もっと簡単な方法は、ブックマークに登録するやり方です。

  1. こちらのブックマークレットのページを表示して、猫化を右クリックしてコンテクスト メニューを出し、ブックマークに登録します。セキュリティうんぬんと出ますが無視します。
    bookmark-step-1.png
  2. 変換したいページを表示させ、先ほど登録したブックマークをクリックすると変換されます。
    bookmark-step-2.png

使用しているソースコード

/**
 * 猫化ブックマークレット
 *
 * 使用方法は、変換を適用させたいページをブラウザに表示させてから
 * アドレス欄に
 * javascript:(function(){var u="http://ブックマークレット設置箇所";var d=document;var s=d.createElement('script');s.charset="UTF-8";s.src=u;d.body.appendChild(s);})()
 * と入力します。
 */

// 名前空間用のオブジェクト window.Zenith が存在しなければ定義
if (!("Zenith" in window))
{
  Zenith = {};
}

// 名前空間用のオブジェクト window.Zenith.Bookmarklet が存在しなければ定義
if (!("Bookmarklet" in window.Zenith))
{
  Zenith.Bookmarklet = {};
}

/**
 * HTML 文章を猫化するブックマークレットの新しいインスタンスを初期化します。
 */
window.Zenith.Bookmarklet.Arisyu = function ()
{
  this.filters = [];
}

/**
 * 指定した要素に変換フィルタを適用します。
 *
 * @param HTMLElement element 対象の要素。
 * @param bool recursive 子要素にも再帰的に適用するなら TRUE、しないなら FALSE。
 */
window.Zenith.Bookmarklet.Arisyu.prototype.filter = function (element, recursive)
{
  for (var i = 0, iLast = this.filters.length; i < iLast; i++)
  {
    var f = this.filters[i];
    
    f.apply(element);
    
    if (recursive)
    {
      for (var j = 0, jLast = element.childNodes.length; j < jLast; j++)
      {
        var child = element.childNodes[j];
        this.filter(child, recursive);
      }
    }
  }
}

/**
 * 指定した要素が持つ全ての子要素のうち、変換対象の要素のテキストを猫化します。
 * 子要素は再帰的に列挙されます。
 *
 * @param HTMLElement element 対象の要素。
 */
window.Zenith.Bookmarklet.Arisyu.prototype.apply = function (container)
{
  this.filter(container, true);
}


/**
 * 正規表現により文章を置換するフィルタの新しいインスタンスを初期化します。
 */
window.Zenith.Bookmarklet.Arisyu.RegExpFilter = function ()
{
  /**
   * 置換候補の正規表現と結果の辞書
   */
  this.dictionaries = [
      {
        "pattern": "な",
        "option": "gm",
        "replace": "にゃ"
      },
      {
        "pattern": "ぬ",
        "option": "gm",
        "replace": "にゅ"
      },
      {
        "pattern": "わたし|ワタシ|わたくし|ワタクシ|私|ぼく|ボク|ぼかぁ|僕|おれ|オレ|俺|じぶん|ジブン|自分|拙者|麻呂",
        "option": "gm",
        "replace": "我輩"
      },
      {
        "pattern": "([!!]+)",
        "option": "gm",
        "replace": "にゃ$1"
      },
      {
        "pattern": "([…+|・+])",
        "option": "gm",
        "replace": "にゃ$1"
      },
      {
        "pattern": "、",
        "option": "gm",
        "replace": "にゃー、"
      },
      {
        "pattern": "(ね?)。",
        "option": "gm",
        "replace": "にゃん。"
      }
    ];
  
  /**
   * 正規表現オブジェクトのキャッシュ
   */
  this.regexpCache = [];
}
  
/**
 * 指定された要素がフィルタの適用対象かどうかを取得します。
 *
 * @param HTMLElement 調べる対象の要素。
 * @return bool 適用対象なら TRUE、対象外なら FALSE。
 */
window.Zenith.Bookmarklet.Arisyu.RegExpFilter.prototype.isAcceptElement = function (element)
{
  var isAccept;
  
  switch (element.nodeName.toLowerCase())
  {
    case 'style':
    case 'script':
    case 'frame':
    case 'code':
      isAccept = false;
      break;
    
    default:
      isAccept = true;
  }
  
  return isAccept;
}

/**
 * 指定された要素へフィルタを適用します。
 *
 * @param HTMLElement 適用対象の要素。
 */
window.Zenith.Bookmarklet.Arisyu.RegExpFilter.prototype.apply = function (element)
{
  if ("parentNode" in element && element.parentNode != null)
  {
    if (!this.isAcceptElement(element.parentNode))
    {
      return;
    }
  }
  
  if (element.nodeName.toLowerCase() != "#text")
  {
    return;
  }
  
  var nodeValue = element.nodeValue;
  
  for (var i = 0, iLast = this.dictionaries.length; i < iLast; i++)
  {
    var dictionary =  this.dictionaries[i];
    
    if (!(i in this.regexpCache))
    {
      this.regexpCache[i] = new RegExp(dictionary.pattern, dictionary.option);
    }
    
    var regexp = this.regexpCache[i];
    nodeValue = nodeValue.replace(regexp, dictionary.replace);
  }
  
  element.nodeValue = nodeValue;
}


////////////////////////////////////////////////////////////////////////////
// ここから実行
////////////////////////////////////////////////////////////////////////////

var arisyu = new Zenith.Bookmarklet.Arisyu();

// フィルタ設定
arisyu.filters = [
    new Zenith.Bookmarklet.Arisyu.RegExpFilter()
  ];

// トップ レベルの文章に適用
arisyu.apply(window.document);

// フレームがあればそちらも適用
if ("frames" in window)
{
  for (var i = 0, iLast = window.frames.length; i < iLast; i++)
  {
    arisyu.apply(window.frames[i].document);
  }
}