レベル: 初級 Sandeep Malik, Tech Lead, IBM
2009年 7月 28日 リッチ・インターネット・アプリケーション (RIA) は従来のデスクトップ・アプリケーションの対話性、応答性、そして堅牢性を Web ベースのアプリケーションにもたらすことを目指しています。そんな RIA は、ビジネス・インテリジェンス (BI) と Web 2.0 の手法をコンテンツと配信に利用しようとしている開発者にとっては特に重要です。RIA ベースのソリューションの最先端を行くアプリケーションとしては、Adobe® Flex® が挙げられます。比較的新しい技術ながらも急速に成長している Flex は、Adobe Flash Player の機能を利用して、極めて応答性の高い UI を備えた最上のグラフィカル・プレゼンテーションを実現します。Flex には便利で堅牢な多数のコンポーネントが付属していますが、Flex が提供する機能の枠を超えて、ドメイン固有の機能を作成しなければならないとなると、難しい事態になってきます。この入門者向けの記事では、Flex レンダリング・エンジンのアーキテクチャーを詳しく見て行くとともに、Flex コンポーネントを RIA に組み込むプロセスをひととおり説明し、Flex の機能を新たに一から作成する際に必要となる知識を提供します。
Flex と RIA について
新しいクライアント・サーバー・モデルが出現し、より複雑な UI が必要になってきたことから、Web ベースの製品でさえも一層リッチで一層応答性に優れたアプリケーション設計が不可欠になってきました。リッチ・インターネット・アプリケーション (RIA) と呼ばれるこの種の新しいアプリケーションは、従来のデスクトップ・アプリケーションの機能と利点の多くを Web ベースのアプリケーションにもたらすことを目指しています。そんな RIA の作成を支援するツールのなかで真っ先に挙げられるのは、Adobe Flex です。RIA の機能には、多くの場合、リッチなグラフ作成機能やドリルダウン動画、3D エフェクト、そしてこれまでになく応答性に優れたユーザー操作が含まれます。このような機能は、ビジネス・インテリジェント (BI) や、Web 2.0 によるコンテンツ作成および配信といった急成長している分野では特に重要です。中核となる Web 2.0 要件には通常、非同期処理、リクエスト・レスポンス・パラダイムの排除、コンテンツの集約などがありますが、Flex のようなアプリケーションを使えば、このような要件に比較的簡単に、そして短時間で対処することができます。
Flex には便利で堅牢な多数のコンポーネントが付属していますが、Flex が提供する機能の枠を超えてドメイン固有の機能を作成しなければならないとなると、難しい事態になってきます。この入門者向けの記事では、Flex レンダリング・エンジンのアーキテクチャーを詳しく見て行くとともに、Flex がもたらす主なメリットについての詳細や、Flex コンポーネントを RIA に組み込むプロセス、そして新しい Flex 機能を一から作成するときに必要な知識を説明していきます。具体的に言うと、Flex とその機能のガイド付きツアーとしてFlex と Java™ プログラミング言語との類似点を一つひとつ検討した後、Flex レンダリング・エンジンの観点から見た Flex コンポーネントのライフサイクルについて説明します。この説明をとおして、Flex コンポーネントのライフサイクルの内容、Flex コンポーネントのコミット、測定、レイアウトの各フェーズに関する細かい注意点を含め、Flex コンポーネントを拡張、作成する際に必要な詳細を掘り下げていきます。Flex コンポーネントが自動的に行ってくれることはたくさんありますが、あらかじめビルドされたコンポーネントを使わないとなると、途端に何をどうすればよいのか戸惑ってしまうはずです。この記事では、ビルド済み Flex コンポーネントが舞台裏で自動的に処理する内容を解説するので、この記事を読めば、コンポーネントの振る舞いを独自に提供しなければないときに何が必要かを把握することができます。
Web 開発標準の変化
Flex や、これと競合する JavaFX、Ajax、Silverlight などの技術は、今日変化しつつある Web 開発手法の先駆けであり、ユーザーが望みどおりにデータを視覚化できるよう、視覚化の方法を変更する上で重要な役割を果たしてきました。これらの Flex およびその競合技術は比較的新しい技術であるため、今でもブラウザー間での移植性、セキュリティー、クライアント・サイドのキャッシングに課せられた制約などの問題を克服するプロセスの途上にあります。
Flex と競合する技術のうち、とりわけ強力なのは Ajax です。この 2 つの技術には重複する機能もありますが、それぞれが得意とする分野はかなり異なります。Flex はリッチで複雑なグラフィカル構造を得意としている一方、Ajax は主にテキスト・ベースのコンテンツに使用されます。Ajax は HTML と密接に連動し、そのデータは XML (または JSON) フォーマットで転送される一方、Flex では XML、JSON、さらにはバイナリー・フォーマットのデータ転送をサポートします。この Flex がバイナリー・データの転送をサポートするという事実は、データ転送速度と簡潔さという点で、Flex を Ajax より遙かに優位にしていると思います。
Flex がもたらすメリット
Flex がもたらす数々の顕著なメリットは注目に値します。Flex のとりわけ大きなメリットの多く (決してすべてではありませんが) は、Flex はユビキタスな Adobe Flash Player をベースにその機能を拡張する、という事実に起因します。Flex を使うことによる主なメリットは、以下のとおりです。
- ブラウザー間での完全な移植性。Flash Player をサポートするブラウザー (つまり、ほぼすべてのブラウザー) は、Flex とそのスクリプト言語である AS (ActionScript) も同じくサポートします。この点は Ajax とは明らかに対照的で、Ajax はブラウザーの種類によって異なる JavaScript 実装の非互換性に影響されます。
- 一貫性のあるルック・アンド・フィール。Flash Player は、すべてのオペレーティング・システムとすべてのブラウザーで同じルック・アンド・フィールを実現することでよく知られています。Flash Player エンジンを利用する Flex は、このルック・アンド・フィールが一貫しているというメリットを継承します。
- 堅牢なセキュリティー。Flex は厳しくテストされた Flash Player セキュリティー・モデルを利用します。
- リッチな UI。Flex は Halo スキン、グラデーション塗りつぶし、ベクター・グラフィックス、そしてその他の Flash Player 機能のメリットを継承します。
- SVG (Scalable Vector Graphics)。Flex が他のほとんどの RIA ベースの技術より抜きん出ている理由は、ベクター描画をサポートし、SVG マークアップ・ファイルを直接組み込めるからです。SVG ベースの画像は、任意のブラウザーがサポートするどの解像度でも同じく見事に表示されます。この点は、所定の画像サイズを拡大すると大幅に劣化するビットマップ・ベースの画像とは極めて対照的です。
- 非同期リクエスト・レスポンス・モデル。Flex は、ユーザー・リクエストの非同期処理を完全にサポートします。Web サイトは非同期処理によって、ユーザー・リクエストのたびに時間のかかるページ・リフレッシュが生じるページ中心のモデルから抜け出すことができます。
- バイナリー・データ通信。Flex では、Flex クライアントとバックエンド・サーバーとの間のバイナリー・データ転送が完全にサポートされます。バイナリー・データの送信には、Adobe 独自仕様の AMF (Action Message Format) (現在はオープンソース仕様) や、他の利用したいフォーマットまたは自社で開発したフォーマットを何でも使用することができます。Flex ではクライアントからサーバーへのバイナリー・ソケット接続を開けたままにすることも可能なので、「真」のデータ・プッシュを実現することができます。ただし、この機能では SSL (Secure Socket Layer) などのブラウザーの暗号化機能を使用できない場合もあることに注意してください。
- ランタイム共有ライブラリー (RSL) とモジュール化。Flex がこの 2 つの機能をサポートするということは、モジュールの動的ロードが確実に可能であるということです。つまり、実行中のアプリケーションに新しい機能を追加できるだけでなく、同じクライアント・マシンで実行中の他の Flex アプリケーションがロードした RSL を使用することさえできます。そのため初期バイナリーのサイズが縮小され、アプリケーションの機能を起動するまでの時間が短縮されます。
- クライアント・サイドのキャッシング。Flex はクライアント・サイドのキャッシングに極めて優れたサポートを提供します。ユーザーの許可があれば、Flex アプリケーションではクライアント・サイドで無制限のデータをキャッシュすることができるため、同じデータが同じセッション、あるいはその後のセッションでリクエストされた場合のネットワーク・ラウンド・トリップが減ります。キャッシュできるデータは、完全なオブジェクト・グラフ、カスタム・クラス、マップ、配列を含め、種類を問いません。このサポートは、HTML cookie よりも遙かに高度です。HTML cookie では、アプリケーションがストリングの名前の値のペアだけしか保管できません。さらに保管できるデータも通常は、Web サイトあたり 4 KB に制限されます。
- ブラウザーに依存しない通信。Flex は、同じタイプのブラウザーで実行中のアプリケーション間、同じブラウザーの異なるタブ間、さらにはユーザーのマシン上にある異なるブラウザーで実行中のアプリケーション間の通信をサポートします。この機能が意味するのは、多数のアプリケーションがデータを共有できるということです。そのため、極めてリッチなエンド・ユーザー・エクスペリエンスが可能になります。
- ストリーミング。Flex はバイナリー・データのストリーミングに対して優れたサポートを提供します。ストリーミング手法では、データの到着した部分から即時、エンド・ユーザーに表示できるため、大量のデータをエンド・ユーザーに転送する必要のあるアプリケーションに大いに役立ちます。
- 強力なバックエンド接続性。Flex は当初から、よく使われている Java Platform Enterprise Edition、Microsoft .NET プラットフォーム、Cold Fusion、PHP などのバックエンド技術に対する優れたサポートを備えています。この接続性サポートが、クライアント・サイドのレイヤーへの Flex 採用を加速化させてきました。
- リッチなフレームワーク。Flex が提供する堅牢なコンポーネント開発フレームワークには、開発者の便宜を図り、多数のすぐに使えるコンポーネントがあらかじめ組み込まれています。これによって、プロジェクトの迅速な開発とタイムリーな配信が容易になっています。
- デバッグおよびエディター・サポート。Adobe では、堅牢な Eclipse ベースのエディターを開発するという非常に賢い決定を行いました。この Flex Builder という名のエディターは、Flex アプリケーションの開発とデバッグを大幅に簡易化します。
以上の特徴を併せ持つ Flex は、現在、そして予測し得る限りの将来にわたり、UI を作成するための非常に強力な選択肢となります。Flex を使用する開発者にとってもう 1 つ大きなプラスとなるのは、Web 2.0 の精神にのっとり、すでに作成されているコンポーネントを統合および再利用して、興味深いソリューションを作成できることです。しかし、新しいカスタム・コンポーネントを一から作成するとなると、事態は複雑になってきます。Flex によって提供される機能は、今まで openGL などのライブラリーを使って作成するデスクトップ・アプリケーションに限られていたリッチで素晴らしいコンポーネントを作成しようという意欲をそそるはずです。例えば、エレクトロニック・アーツ社のレーシング・ゲーム、「ニード・フォー・スピード シフト」のコンセプトをベースにしたゲーム・エンジンを Web 上で作成できる可能性を考えてみてください。あるいは、データ・フローとリンクの使用をリアルタイムで見られる ISP トポロジーを構築したくなる可能性もあります。Flex の未来には、この例に限らずさまざまなアプリケーションが生まれてくると思いますが、市場とツールセットはまだそこまでの段階には至っていません。いずれにしても、Web ベースの UI という波が押し寄せているエンタープライズ・ビジネスでは、Flex が提供する機能から多大なメリットを得られるはずです。
Java プログラミング言語との類似点
Adobe は、リッチな UI をレンダリングする技術として実績のある Flash Player をベースに Flex を作成しました。Flash Player は現状のとおり、グラフィック・デザイナー向けのブラウザー・プラグインであり、MovieClip、タイムライン、バナー広告などを作成する方法を単純化します。しかし、オブジェクト指向の設計により、他のコンポーネントを継承するコンポーネントを使ってアプリケーションを開発することに慣れているエンタープライズ開発者にとって、Flash Player アプリケーションはそれほど魅力的には映りません。その一方、Flash Player のネイティブ言語である ActionScript は、JavaScript に似た ECMA 準拠のスクリプト言語です。設計者は XML タグ・ベースの言語に執着する傾向があるため、Adobe はこの点を即座に認識し、MXML を導入することによってその隔たりを埋めました。Adobe の Flex での手法は、Sun™ が Servlets と JSP 技術で取った手法と似ていて、MXML で作成されたコードを ActionScript コードに変換した上で、ABC (Action Byte Code) にコンパイルするというものです。これは、Java バイト・コードのプロセスで行われることと大して変わりありません。ABC はバイナリー SWF フォーマットにパッケージ化され、ユーザーが Web ブラウザーでリクエストした時点で配信されます。この SWF ベースのファイルは、ローカルで実行することも、スタンドアロンの Flash Player プラグインで実行することもできますが、Flash のセキュリティー・モデルでは、ローカル・ファイルがネットワーク・リソースにアクセスすることも、その逆のアクセスも許可していません。
堅牢なセキュリティー・モデル
このセキュリティー対策は、ネットワークで配信されるアプリケーションがローカル・クライアントのファイル・システム (Java Applets が多大な悪評を得ることとなった機能) にはアクセスできないことを意味します。JVM に main メソッドが必要なのと同じく、Flex アプリケーションにはエントリー・ポイントとして mx.core.Application を継承するコンポーネントが必要です。そして JVM 内で稼働する Java アプリケーションのように、SWF ファイルもはやり AVM (ActionScript Virtual Machine) と呼ばれる仮想マシン内で実行されます。表 1 に、Java プログラミング言語と Flex との仮想マシンの類似点を簡単に比較します。
表 1. Java と Flex の VM の比較
| Flex | Java |
|---|
| MXML タグおよびコンポーネント | JSP タグおよびコンポーネント | | ActionScript ファイルとして変換 | サーブレット・クラスとして変換 | | ABC (ActionScript Byte Code) にコンパイル | JBC (Java Byte Code) にコンパイル | | 最終結果は .swf ファイル | 最終結果は .class ファイル | | ActionScript 仮想マシンで実行 | Java 仮想マシンで実行 |
Flex でのクラス階層
Flex には 1 回の記事では取り上げられないほど多くのクラスが組み込まれています。けれども、いくつかのクラスを詳細に調べてみると、これらのクラスが Flex オブジェクト・モデルに提供する内容を理解する上で参考になります。図 1 に、コア・クラスの階層を示します。
図 1. Flex のコア・クラスの階層
最上位のクラスは DisplayObject です。Flex はこのクラスを Flash Player の Stage オブジェクト (汎用表示リスト) に追加します。InteractiveObject は、キーボードおよびマウス・イベントを含めたユーザー操作を処理します。DisplayObjectContainer では、子コンポーネントを境界内の任意の場所に追加したり、移動したりすることができます。Sprite および FlexSprite は基底クラスなので、タイムラインは必要ありません。この 2 つの基底クラスを継承して独自のクラスを作成することもできますが、この一連のクラスのなかでどれが真の主役かと言えば、それはカスタム・コンポーネントを作成するために必要な基本フレームワークを提供する UIComponent です。通常はこのクラスを継承してカスタム・コンポーネントを作成します。この記事のサンプル・プロジェクトでも継承しているのは、このクラスだけです。
Flex は、作成されたすべてのコンポーネントを、最終的に Flash Player によって処理される単一の汎用表示リストの子コンポーネント (および孫コンポーネント) として追加します。Flex のアーキテクチャーはこれらの複雑な詳細を開発者から隠しながらも、開発者にイベント・リスナーへのフックを提供し、高度な段階でイベントを処理できるようにします。Flash Player の堅牢なイベント・ディスパッチ・メカニズムでは、何万ものイベントを処理することができます。
単純な Flex コンポーネントの作成
ここまでのところで、Flex が Web 開発にもたらす主なメリット、Flex の基本アーキテクチャー、そしていくつかのコア・クラスについて説明しました。しかしそのすべては、開発者にとって極めて重要な内容の前置きに過ぎません。その内容とは、コードの作成およびソリューションの構築です。Flex で単純なコンポーネントを作成するのは、ごく簡単なことです。例えば、一組のテキスト入力ボックスを作成し、ユーザーがいずれか一方の入力ボックスにテキストを入力すると、両方のテキスト・ボックスにそのテキストが表示されるようにしたいとします。このようなコンポーネントを作成するには、1 行たりともイベント処理コードを書く必要はありません。この単純な UI は、図 2 のようなものです。
図 2. 2 つのインタラクティブ・テキスト・ボックス
この単純なアプリケーションは、リスト 1 に記載するたった数行のコードで作成することができます。
リスト 1. 単純な Flex UI の作成
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:TextInput id="box1" text="{box2.text"}>
<mx:TextInput id="box2" text={"box1.text}
</mx:Application>
|
上記のコードは、この上なく単純な (そして簡潔な) コードですが、その見掛けにだまされないでください。実はこの数行で、かなりのことが行われています。Flex が自動的に行ってくれるすべての内容を見てみたい場合は、どうぞ遠慮なく、このコードをコンパイラー・フラグ keep-generated-actionscript=true を指定してコンパイルしてみてください。Application コンテナー (mx:Application タグ) と TextInput コントロールが、かなりの量の複雑なコードを隠していることがわかるはずです。皆さんの上司が既存の Flex コンポーネントを拡張、再利用、そして統合することに異存がない限り、これで問題ありませんが、独自のコンポーネントを一から作成し始めると途端に事態は複雑になってきます。カスタム・コンポーネントを作成してみると、Flex がいかに多くのことを自動的に引き受けてくれているかが明らかになります。こうしてありがたみがわかった Flex 機能の多くは、自分で作成する必要最小限の Flex コンポーネントにはない機能です。
カスタム・コンポーネントを作成する理由
Flex の完成度が高くなるにつれ、より多くの開発者が独自のカスタム Flex コンポーネントを作成するようになってくるはずです。その理由としては、以下の 2 つが挙げられます。
- ほとんどの Flex コンポーネントは一般向けなので、既存のコントロールでは特定の要件に対処できない場合があります。既存のコンポーネントの外観を変更 (スキニング) したり、拡張したりすることで、ある程度のギャップを埋めることはできますが、特定コンポーネントの振る舞いに根本的な変更を及ぼさなければならない場合には、独自のコンポーネントを作成することが不可避となります。
- 多くの新しい視覚化パラダイムでは、まだサポートされていないフォーマットや、存在もしていない新しいフォーマットでデータを表示することが必要になってきます。このような新しいフォーマットをアプリケーションで利用するには、そのための機能を自分で提供しなければなりません。
Flex コンポーネントのライフサイクル
独自のカスタム・コンポーネントを作成しようと思ったら、Flex コンポーネントのライフサイクルを理解することが必須となります。Flex のライフサイクルは、LayoutManager が中心となって動かします。以下は、Flex API からの引用です。
「LayoutManager は、Flex の測定およびレイアウト・ストラテジーの背後にあるエンジンです。レイアウトは、コミット、測定、レイアウトという 3 つのフェーズで行われます」。
コミット・フェーズの内容
最初のフェーズであるコミットは、Flex が DisplayObject クラスにある addChild() またはその変形のいずれかを使ってコンポーネントをメインの表示リスト (以降、「ステージ」と呼びます) に追加した時点で始まります。Flex はこのリストにコンポーネントを直接追加しないことに注意してください。リストにコンポーネントが追加されるまでには、いくつかの中間ステップが発生します。その最初のステップは、Flex によるコンポーネントの全プロパティーの検証です。LayoutManager が親オブジェクトの validateProperties() を呼び出し、続いて子オブジェクトのそれぞれで繰り返しこのメソッドを呼び出します。この処理順は、トップダウン順と呼ばれます。「dirty」プロパティーがある場合、LayoutManager では検証が行われる前に、これらのプロパティーをコミットできるようになっています。その場合に呼び出すのは、UIComponent の保護されたメソッド commitProperties() です。カスタム・コンポーネントでは、このメソッドをオーバーライドして、検証が行われる前にプロパティーをコミットしなければなりません。Label コントロールを調べると、この場合に何が行われるかをより深く理解することができます。ラベルとは、アプリケーションで 1 行のテキストをレンダリングするテキスト・フィールドのことです。ラベルでは、フォント、フォント・サイズ、フォントの色、フォント・ファミリー、テキストなどの値を変更することができます。ラベルでフォント関連の変更を行うには、新しいフォント・コンテキストと、その新しいフォントに対応する新規の UITextField を提供する必要があります。この要件は、新しいフォントが適切にレンダリングされることを確実にするためのものです。
コミットの変更に対する Flex の対処方法
ここで、アプリケーションがラベルのフォントを変更するとどうなるかを考えてみてください。Flex の UI は非同期処理に依存することから、変更はすぐには適用されない場合があります。フォントを変更するときには、それと同時に新しい UITextField を作成することができます。あるいは、新しいフォント・コンテキストと、フォントが変更されたことを示すフラグを保存するという方法もあります。こうすることによって、コンポーネントが次にレンダリングされるときに (通常は、次の画面更新時)、フォントの変更を適用することができます。つまり、フォント・ステータスの変更を示すフラグが変更されているかどうかをチェックして、変更されている場合には、その時点で対応する UITextField を作成すればよいだけのことです。画面が次に更新されるまでに何度もプロパティーが変更される可能性もありますが、その場合には、変更をすぐに処理するのでは効率的でありません。それよりも賢いストラテジーは、次の画面更新が行われるまで待ってから、変更を適用することです。通常の頻度で画面の更新が行わるのであれば、エンド・ユーザーは何も気付きません。これは、非同期のイベント駆動型 UI ではよく使う方法で、アプリケーションで適用する必要のある変更を保存し、変更をキューに入れ、そして適切なタイミングが来たら、変更を適用します。適切なタイミングの間近であることをどうやって知るのか疑問に思うかもしれませんが、まさにこれに対処するのが、LayoutManager です。コンポーネントの commitProperties() に対するコールバックを受信することにより、これが、コンポーネントが再度レンダリングされる前の最後の呼び出しであることがわかります。この呼び出しの後にプロパティーに対して行われた変更は、いずれもこの時点での画面の更新には現れません。これらの変更は、その次の画面の更新とその次の commitProperties() 呼び出しまで待つことになります。
それはそうとして、LayoutManager にコンポーネントの検証を再度実行するように指示するにはどうすればよいのか疑問に思っていることでしょう。例えば、誰かがラベルのフォントを変更したため、その新しいフォントと関連フラグを保存したとします。この場合、LayoutManager に検証フェーズをもう一度開始させるには、該当するコンポーネントに対して invalidateProperties() というメソッドを呼び出します。ここで、ラベルのテキストを変更するために必要なコードを調べてみましょう。リスト 2 に、検証フェーズを実行する方法を記載します (簡潔にするため、一部のコードは省略しました)。
リスト 2. 検証フェーズの実行
public function set
text(value:String): void {
_text = value;
textChanged = true
invalidateProperties();
}
|
_text は一時的に新しいテキスト値を保存するフィールドで、textChanged はこのテキストが変更されたことを表すためにセットされるフラグです。また、LayoutManager に検証フェーズを開始するよう通知するために、invalidateProperties() を呼び出していることにも注意してください。リスト 3 に、Label クラスの commitProperties() 内の対応するコード・スニペットを記載します (簡潔にするため、一部のコードは省略しました)。
リスト 3. commitProperties の使用
override protected function
commitProperties():void{
if (textChanged) {
textField.text = _text;
textChanged = false
}
|
このオーバーライドされたメソッドを見ると、textChanged フラグが true にセットされた場合、実際の UITextField (textField) が更新されてから、以降のテキストの変更を表すために false にリセットされることがわかります。Flex を使用して独自のコントロールを作成するには、この点を理解しておくことが重要です。リスト 2 とリスト 3 で説明した検証フェーズは、Flex レンダリング・エンジンに欠かせない 3 本柱のうちの 1 つを表します。これはまた、無視されることの多いフェーズでもあり、この検証フェーズに触れることなく作成された高度なコンポーネントは数多く目にするはずです。このフェーズが無視される理由の 1 つは、おそらく効率性を考慮してのことでしょう。変更されると同時にその変更を「適用」すれば、失敗の可能性はほぼありません。けれども、誤って行われた変更であったり、変更が必要以上に何度も適用されたりしたとしたら、Flex アプリケーションのパフォーマンスに多少の問題がもたらされること (あるいは大きな問題になる可能性もあること) を肝に銘じておくことが重要です。
一般に、開発者がコンポーネントを作成するコンテキストは 2 つあります。最初のコンテキストでは、アプリケーションの使用ケースと密接に結び付いたコンポーネントを作成します。このようなアプリケーションは大抵、再利用できるほど一般的なアプリケーションではないため、コンポーネントを過度に複雑にする代わりに、ところどころで多少の効率性が失われても問題ありません。2 番目のコンテキストでは、フレームワーク・レベルで使用したり、他の開発者が使用したりするためのコンポーネントを作成します。この場合のコンテキストでは、コンポーネントを最大限効率化することが有益です。検証フェーズへの対処は、おそらくこの 2 番目のコンテキストの部類に入ります。
測定フェーズの詳細
測定フェーズも Flex の重要なフェーズであり、このフェーズはコミット・フェーズに比べ、より広く使用されています。コンポーネントが検証されると、LayoutManager はそのコンポーネントの測定に移り、特定のコンテキストで表示できるようにします。例えば、LayoutManager はコンポーネントを何らかのコンテナーの子として表示する場合もあります。この測定フェーズが必要となる理由は、LayoutManager が親コンテナーにスクロール・バーが必要かどうかを判断する上で役立つからです。コンポーネントによっては、子コンポーネントを適切に表示するために必要なスペースに応じて、自動的にそのサイズを変更することができます。VBox を例に用いると、このコンテナーはその子コンポーネントを、前のコンポーネントの下に配置されるように、縦に並べて行きます。VBox に height の値も width の値も割り当てなければ、子コンポーネントを追加するたびに、そのサイズは変更されます。VBox のコンテナーの height は、サイズの変更を不可能にする何らかの制約を指定しない限り、縦に並べられたすべての子コンポーネントのサイズを含めるのに十分です。コンポーネントを測定するには、Flex に組み込まれている measure() という保護されたメソッドを使用することができます。LayoutManager はこのメソッドを必要に応じて (通常、次の画面更新の前に) 呼び出すので、その時点でコンポーネントを測定し、measuredWidth と measuredHeight を使って height と width の値を設定する必要があります。すると、親コンテナーはこの 2 つのプロパティーを使ってコンテナー自体を測定します。
子を先に測定すること
検証フェーズとは反対に、測定フェーズはボトムアップ順で行われなければなりません。これは、子コンポーネントを測定してからでないと、親コンテナーを測定できないからです。ボトムアップ順であれば、親コンテナーが測定のための呼び出しを受け取ったときに、その子コンポーネントがすでに測定済みであることが確実になります。ここまでは問題ありませんが、コンポーネントの height と width を明示的に指定したとしたらどうなるでしょうか。Flex は、これらの明示的に指定されて保管された height と width の値を explicitWidth または explicitHeight プロパティーに割り当てます。このように、値が指定されていれば Flex は指定された width の値と height の値に従いますが、コンテナーに明示的なサイズを設定するのは良策ではないこともあります。なぜなら、コンテナーのすべての子の合計サイズを予測できない場合もあるためです。そのような場合には、Flex は指定された境界に収まらない子のためにスクロール・バーをコンテナーに追加します。これらのスクロール・バーは、Container クラスを継承するコンテナーにしか表示されないことに注意してください。その他のコントロールには、スクロール・バーが表示されることもあれば、Flex がコンテンツ領域を削り (コンテンツ・クリッピングと呼ばれるプロセス)、指定されたサイズを維持することもあります。
明示的なプロパティー値の設定
width と height の値を明示的に設定すると、興味深い問題が生じてきます。例えば、他のコンポーネントが含まれることになるコンポーネントを作成していて、このコンテナーのサイズを測定しなければならない場合、それぞれの子の width と height がわかると助かります。そこで疑問になるのが、特定のコンポーネントのサイズが明示的に指定されているのか、あるいは測定メソッドをオーバーライドして測定されたのかを知る手段です。測定されたサイズは measuredWidth や measuredHeight などのプロパティーに含まれる一方、明示的に定義されたサイズは explicitWidth および explicitHeight に含まれることを思い出してください。つまり width と height の対のうち、一方の対にだけ実際の値が含まれ、もう一方の対には NaN (Not-a-Number) が含まれることになります。Flex はこの難題を解くために、getExplicitOrMeasuredWidth() と getExplicitOrMeasuredHeight() という 2 つのメソッドを提供しています。この 2 つのメソッドを呼び出すだけで、height と width の値が測定されているのか、それとも明示的に設定されているのかを考える必要はなくなります。コンポーネントが測定されたら、setActualSize() メソッドを呼び出して width および height プロパティーを設定してください。height と width の値が明示的に設定されている場合でも、これらのプロパティーは設定されます。リスト 4 を見るとわかるように、width と height の値を明示的に設定したテキスト入力コンポーネントを作成するのは簡単です。リスト 4 では、4 つのプロパティーを受け入れます。
リスト 4. width および height の入力
<mx:TextInput width="200" height="45"/>
|
この例の場合に設定されるプロパティーは、width、height、explicitWidth、explicitHeight の 4 つです。measuredWidth と measuredHeight は NaN になることに注意してください。
今度は height の値も width の値も明示的に設定しないリスト 5 を見てください。
リスト 5. コンポーネントによる測定
この例では、コンポーネント自体が測定を行います。Flex フレームワークの他の領域と同じく、「デフォルト」の width 値と height 値がそれぞれ measuredWidth と measuredHeight として割り当てられ、明示的な値は NaN になります。いずれの場合も、getExplicitOrMeasuredWidth() と getExplicitOrMeasuredHeight() が返すのは正しい値です。Flex は同じような流れで、コンポーネントの最小の幅と最大の幅を指定する measuredMinWidth または measuredMaxWidth に値を設定します。コンポーネントのサイズが maxWidth より大きくなると、現行のビュー領域では可視でないコンテンツを表示するためのスクロール・バーが表示されます。サイズを明示的に設定すると、LayoutManager が measure() を呼び出さないことに注意してください。測定の値を明示的に定義すれば、コンポーネントを再び測定する必要はなくなるため、これは当然のことです。
LayoutManager に測定フェーズを開始するように指示するのは、invalidateSize() の呼び出しです。LayoutManager は、invalidatePropertiesQueue、invalidateSizeQueue、invalidateDisplayListQueue という 3 つの個別のキューを保持します。これらのキューはそれぞれ、ライフサイクルのある時点で invalidateProperties()、invalidateSize()、invalidateDisplayList() を呼び出すコンポーネントに対応するキューです。この呼び出しが行われると、LayoutManager はそれぞれのキューのコンポーネントを処理し、コンポーネントごとに validateProperties()、validateSize()、および validateDisplayList() メソッドを呼び出します。続いて UIComponent 内のこれらのメソッドのデフォルト実装が commitProperties()、measure()、および updateDisplayList() を呼び出します。これらのメソッドはそれぞれカスタム・ロジックを作成してオーバーライドすることができます。
コンポーネントを測定するには、テキストの測定も必要になります。しかしテキストの振る舞いは他のコントロールとは異なります。テキストの測定では、アセント (訳注: ベースラインから行の最上部までの長さ)、ディセント (訳注: ベースラインから行の最下部までの長さ)、レディング (訳注: 行間の垂直距離)、ガター (訳注: コンポーネントの間隔) などを考慮しなければなりません。幸い、Flex にはこのプロセスを簡単にするユーティリティーが用意されています。UIComponent が公開する、テキスト・コントロールの測定を支援するための measureText() と measureHtmlText() というメソッドです。リスト 6 に、テキスト・フィールドを測定する方法を示します。
リスト 6. テキスト・フィールドの測定
package components {
import flash.text.TextLineMetrics;
import mx.core.UIComponent;
public class MyLabel extends UIComponent {
private var _text : String;
public function set text(value :
String) : void {
_text = value;
invalidateSize()
}
public function get text() : String {
return _text
}
override protected function measure(): void {
if (!_text) return super.measure();
//measure text here!
var textLineMetrics : TextLineMetrics =
super.measureText(_text);
super.setActualSize(textLineMetrics.width,
textLineMetrics.height);
}
|
上記のコードは、開発者が測定フェーズに専念できるように、UIComponent を継承する単純な MyLabel クラスを作成します。テキストを設定するときに invalidateSize() を呼び出していることに注目してください。このメソッドが LayoutManager に対し、次の画面更新の際にカスタム・コンポーネントを invalidateSizeQueue() に追加するように指示します。これによって、LayoutManager は UIComponent に定義されているカスタム・コンポーネントの validateSize() メソッドを呼び出します。そして最後に validateSize() が呼び出す measure() はすでにオーバーライドされているので、この呼び出しを機に、コントロールのサイズを設定することができます。単純に width を 200 に、height を 45 に設定してから、その結果を受け入れることもできますが、MyLabel クラスのすべてのインスタンスに同じサイズを共有させるというつまらないやり方では、そもそも Flex のような堅牢な UI 開発ツールを使用する意味がなくなってしまいます。しかし、固定サイズを使用すれば、measure() メソッドをオーバーライドする必要はありません。さらに、コンポーネントを親に追加するときに width と height を定義することさえできます。リスト 7 に、ラベル・コントロールの測定値を取得する際のバリエーションをいくつか記載します。
リスト 7. ラベル測定値の取得
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
xmlns:components="article1.components.*">
<components:MyLabel id="label1"/>
<components:MyLabel ="label2" width="200"
height="50"/>
</mx:Application>
|
上記の例の label1 では、サイズを明示的に定義していないため measure() メソッドが呼び出されることになります。その一方、label2 の場合には measure は呼び出されません。コントロールのサイズを明示的に定義しているためです。Flex の Label クラスでの measure() 実装は、measureText() を呼び出す以上のことを行っている点に注意してください。上下左右のパディングは開発者が処理しなければなりませんが、基本原理はそのまま引き継がれます。
測定フェーズは、Flex コンポーネントのライフサイクルでは非常に重要なフェーズとは言え、独自のコンポーネントを一から作成するのでない限り、このフェーズに重点を置く必要はめったにありません。大抵のカスタム Flex コンポーネントでは、このフェーズを無視し、Flex に処理を任せることになるでしょう。Flex はこの種のニュアンスを巧みに処理し、開発者を厄介な測定タスクから解放してくれます。その一方で、Flex フレームワークにまったく新しいコンポーネントを作成したいのであれば、このフェーズを十分に理解しておくことが重要です。
レイアウト・フェーズ: ここでアクションが起こります
レイアウト・フェーズは、Flex フレームワークの 3 つのライフサイクル・フェーズでは 3 番目のフェーズです。このフェーズに費やす時間はかなり長いので、これが 1 つのフェーズであるとは考えられないほどです。スキニングと見栄えに関わる拡張を含め、グラフィックスに関するすべての作業はこのフェーズで行われます。
例えば、検証および測定が正常に完了したコンポーネントがあるとします。この場合、次のステップとなるのはコンポーネントをレイアウトすることです。レイアウト・フェーズはコンポーネントのさまざまな側面を処理します。このフェーズの間に、例えばすべての背景グラフィックスを処理したり、スキンが必要なコンポーネントをラップしたりします。コンポーネントを目的の位置に移すのも、このフェーズでの作業です。垂直レイアウトの VBox コンテナーの例をもう一度考えてみてください。このコンテナーに子コンポーネントを追加するときには、子コンポーネントを縦に並べなければなりません。この移動は、このレイアウト・フェーズで行います。
Flash 座標系の仕組み
これから Flex を使って VBox のなかに子コンポーネントを移動する手順を説明しますが、その前に、Flash Player の座標系について説明しておきます。いずれのコンポーネントでも、その左上隅が x 値と y 値によって表されます。各コンポーネントの x 値と y 値は、その直接の親との相対値です。x 軸は右方向が正の値となり、y 軸は下方向が正の値となります (これも、従来の上方向が正の値という方式とは対照的です)。したがって、ラベルを VBox 内に配置し、その x 値と y 値がそれぞれ 30 と 40 である場合、ラベルの左上隅は VBox 自体の左上隅から右に 30 ピクセル、下に 40 ピクセルの位置となります。各コンポーネントには mouseX および mouseY プロパティーがあり、この 2 つのプロパティーによって、コンポーネントに対するマウス・ポインターの相対位置が示されます。例えば、mouseX が -46 で、mouseY が 78 であれば、コンポーネントの座標系ではマウス・ポインターは左に 46 ピクセル、下に 78 ピクセルの位置にあることになります。Flex はローカル座標系に加え、アプリケーション全体で左上隅からの x 座標と y 座標を測定するグローバル座標系も備えています。これらの座標は globalX および globalY と呼ばれます。
Flex によって管理される多くの変更
コンポーネントが最終的に画面に表示されるまでには、そのライフサイクルのなかで一連の変換、回転、拡大縮小、切り取り、引き伸ばしが行われます。各コンポーネントには、これらの調整に関する情報を保持する matrix プロパティーが関連付けられます。したがって、これらのマトリックス変換が行われる間、x と y および globalX と globalY は相互に関連付けられるというわけです。通常、x と y を取得するにはマトリックス変換を globalX と globalY に適用します。そして globalX と globalY の値を x と y から取得するには、逆の変換を行う必要があります。都合のよいことに、Flex はこれらの詳細を隠し、変換を簡単に実行できる 2 つのメソッド、localToGlobal() と globalToLocal() を用意しています。基本となる変換について考えなくても、この 2 つのメソッドは問題なく使用できますが、1 つだけ重要な注意点があります。それは、関数はコンポーネント・レベルではなく、ポイント・レベルで機能することです。ここで再び、VBox に基づく例を検討してみましょう。リスト 8 は、VBox 内部にラベルを作成するコードです。
リスト 8. VBox に含めるラベルの作成
<mx:VBox id="box">
<mx:Label id="myLabel" text="I am a label"/>
</mx:VBox>
|
box と myLabel ではどちらも、localToGlobal() メソッドとその逆を行うメソッドを使用することができます。例えば、myLabel のグローバル・ロケーションを取得したいとします。この場合、myLabel の x と y はその親である box の x と y に相対することから、myLabel の親の localToGlobal を使用してロケーションを取得するという方法を考え付くかもしれません。一方、myLabel でも同じメソッドを使えるので、このメソッドを呼び出すという方法も考えられます。いずれの方法も正解ですが、それぞれのメソッドは異なるパラメーターで呼び出さなければなりません。この例で使用できるのは、box.localToGlobal(new Point(myLabel.x,myLabel,y)) または myLabel.localToGlobal(new Point(0,0)) のいずれかです。box.localToGlobal(new Point(myLabel.x,myLabel.y)) を呼び出す場合は、Flex に VBox 内でのポイントのグローバル・ロケーションを提供するように指示していることになります。つまりその座標は myLabel.x と myLabel.y です。これは myLabel とは何の関係もないことに注意してください。
正確なグローバル座標の取得
myLabel の x 座標と y 座標がそれぞれ 40 と 30 だとします。この場合に要求しているのは、VBox 内でのポイントである (40,30) という正確なグローバル・ロケーションです。そのため、ここで適用される変換は VBox マトリックスの変換ということになります。myLabel の座標系の左上隅は (0,0) の位置にあります (これは、あらゆる Flex コンポーネントに当てはまります)。つまり、myLabel の座標系を考慮した 2 番目の 方法では、Flex に myLabel の座標系の (0,0) に位置するポイントのグローバル・ロケーションを提供するように要求することになります。方法は違っていても、出される答えは同じです。基本的には同じポイントを 2 つの異なる座標系から取得しているというだけに過ぎません。この違いを理解しておかなければ、後でエラーと混乱の元となります。
ツールチップの追加
いくつかの重要な理由により、Flex を使用する際にグローバル・ロケーションを取得しなければならない場合があります。その一例として、コンポーネントにツールチップを表示するという一般的なケースを考えてみてください。ツールチップは Flex アプリケーションの systemManager に追加されるため、グローバル座標系のなかにあります。けれども、ツールチップを表示する対象となるコンポーネントがあるのは、親のローカル座標系です。そのため、Flex では対象コンポーネントの右下隅近くのポイントのローカル座標を選択し、「ローカルからグローバル」への変換を適用します。そして最後にツールチップ・コンポーネントが描画され、対象コンポーネントのツールチップである子コンポーネントとして追加されます。ツールチップは Flex アプリケーションの systemManager に追加されてから、グローバル・ポイントに配置されて計算されるというわけです。別のケースとして、ブラウザーの左上隅にある File メニューなどのコントロールがクリックされた後に、メニューを表示する場合はどうなるでしょうか。このようなメニューは、File オプションを示すラベルに関連するコンポーネントの子として追加することはできません。例えば、メニューをラベルの親の子として追加すると、Flex はコントロールをラベルのポイントの真下に配置してしまいます。これが、VBox のデフォルトの振る舞いだからです。メニューをラベル自体の子として追加した場合には、ラベルはそれが保持するテキストを測定し、テキストだけが収まるサイズを設定するため、メニューがまったく表示されないことも考えられます。代わりに必要な手段はsystemManager にメニューを追加することです。するとメニューは、ポップアップ表示される子コントロールと同じように振る舞います。systemManager にコントロールを追加すると、アプリケーションの領域内であれば、そのコントロールをどこにでも配置することができます。このようなコントロールをラベルの真下に配置するには、目的のロケーションをグローバル座標に変換する必要があります。例えばラベル下端のポイントは、ラベルの座標系では (0,label.height) にあります。このポイントをラベルの localToGlobal() を使って変換した後、メニューをそのポイントに配置します。move() メソッドを使ってメニューを配置すると、コンポーネントの左上隅が目的のロケーションに来るようになります。リスト 9 に、ラベル・コントロールにメニューを追加する方法を示します。
リスト 9. ラベルへのメニューの追加
<?xml version="1.0" encoding="utf-8"?>
<mx:Label xmlns:mx="http://www.adobe.com/2006/mxml"
click="showMenu()">
<mx:Script>
<![CDATA[
import mx.controls.Menu;
private function showMenu() : void {
var m : Menu = new Menu();
// populate the menu control with options:
systemManager.addChild(m);
var global : Point = localToGlobal(new
Point(0,height));
m.move(global.x,global.y);
}
]] >
</mx:Script>
</mx:Label>
|
このサンプル・コードでは、ラベルをクリックするとイベント・ハンドラーが呼び出され、それによって新しいメニュー・コントロールが作成され、そのコントロールにはデータが入力されて (簡潔にするため、このコードの一部は省略してあります)、systemManager の子として追加されます。このコンポーネントは、systemManager の rawChildren のリスト内に表示されます。このコードでは最後に、メニュー・コントロールをラベル・コントロールの真下のグローバル座標に移動します。ローカル座標系からグローバル座標系への変換によって、対象コントロールの適切な配置が可能になることに注意してください。
コンテナーを操作しているときには、ローカル座標系とグローバル座標系に加え、もう 1 つ考慮しなければならない座標系があります。コンテナーは、コントロールの子を保持するコンテンツ・ペインを自動的に作成します。そのため、コンテナーに追加された子はコンテナーのローカル座標系でなく、独自の座標系を持つコンテナーのコンテンツ・ペインに配置されます。そもそもコンテンツ・ペインがある理由、そしてなぜこれが必要なのか疑問に思うかもしれませんが、Flex はコンテナーを追加するだけでなく、そのコンテナーのなかにスクロール・バーや境界線、そしてオーバーレイなどの多数の要素を追加できる必要があります。このような要素がコンテナーに追加されたときに、コンテンツ・ペインがあれば、子の追加だけを処理できるからです。スクロール・バーにより、コンテナーの可視領域内で可視になっていない子でも、コンテンツ・ペインには存在します。このような項目をローカル座標系からコンテンツ座標系に変換するためには、追加のメソッドが必要となります。それに該当するのが、localToContent() メソッドと contentToLocal() メソッドです。ただし、コンテンツ座標系が一般に使用されるのは、コンテナーが子の絶対位置決めを使用する場合です。
レイアウト・フェーズの通知
コミット・フェーズや測定フェーズと同じく、レイアウト・フェーズは invalidateDisplayList() が呼び出されることによって通知されます。この呼び出しは LayoutManager に、もはや現行の表示リストは有効でないため、更新の必要があることを通知します。Flex は、LayoutManager に対して通知するコンポーネントを invalidatedDisplayListQueue に追加し、この命令を次のレイアウト UI 更新で処理します。LayoutManager がキュー内の各コンポーネントで validateDisplayList() を呼び出すと、このメソッドが保護されたメソッド updateDisplayList() を呼び出します。カスタム・レイアウトを作成している場合には、このメソッドを super.updateDisplayList() を呼び出した後にオーバーライドし、必要なロジックで置き換えなければなりません。特に注意が必要なのは、updateDisplayList() が引数として取るパラメーター unscaledWidth と unscaledHeight です。この 2 つの値の名前から、このコンポーネントのコンテンツを配置しなければならない場所の検討がつきます。この 2 つのパラメーターの組み合わせによって、対象コンポーネントの境界が定義されるからです。これらの境界は拡大縮小されないことに注意してください。拡大縮小は、このメソッドの実行が完了した後に Flex が自動的に行います。必要な場合には、scaleX および scaleY プロパティーを使ってベクター描画の縮尺を変更することもできます。デフォルトでは、scaleX と scaleY の値は 1 です。すべてのコンポーネントには、Flex がベクター描画を行うときに使用する保護された graphics オブジェクトがあります。ベクター描画は、背景や境界線の色の塗りつぶし、フィルやストロークへのグラデーションの指定など、さまざまな処理で使用されます。ベクター・ベースの描画はそれ自体が大きなトピックであり、この記事の範囲を超えてしまいます。ここでは、このグラフィックス・オブジェクトが公開する drawCircle() などのメソッドを使用して、基本的な形状と境界を描画できるとだけ言っておけば十分でしょう。例えば、Flex の Label コントロールを拡張するために、このコントロールの境界と背景の色を作成するとします。この 2 つのオプションはいずれも標準 Label コントロールの実装には用意されていませんが、リスト 10 を見ると、その作成方法がわかります。
リスト 10. Flex の Label クラスの継承
package article1.components {
import mx.controls.Label;
public class MyLabel extends Label {
override protected function updateDisplayList(
unscaledWidth:Number, unscaledHeight:Number): void {
super.updateDisplayList(unscaledWidth,unscaledHeight);
graphics.lineStyle(1);
graphics.beginFill(0xcccccc);
graphics.drawRoundRect (0,0,unscaledWidth,unscaledHeight,5,5);
graphics.endFill();
|
このコードは Label クラスを継承して updateDisplayList() メソッドをオーバーライドします。このなかで描画しているのは角丸長方形です。その width は unscaledWidth に、height は unscaledHeight に設定し、角を丸めるための 2 つのパラメーター、ellipseWidth と ellipseHeight は 5,5 に設定します。そしてこのスニペットは最後に、長方形の背景をグレーに塗りつぶします。お気付きかもしれませんが、このコードでは invalidateDisplayList() を呼び出していません。Flex の Label クラスを継承するクラスでは、この呼び出しは不要です。Flex ではさまざまな場所で無効化が自動的に行われるからです。例えば、リスト 11 のコードのスコープを調べてみてください。このコードは、前のスニペットが継承する Label クラスからの抜粋です。
リスト 11. 基底クラスでの無効化
// some code omitted for clarity
public function set text(value:String): void {
_text = value;
textChanged = true
invalidateProperties();
invalidateSize();
invalidateDisplayList();
|
ラベルのテキストを設定するときに、Flex が 3 つすべての無効化メソッドを呼び出していることに注目してください。つまり、これらのメソッドを明示的に呼び出す必要はないということです。参考のために、この 3 つすべての無効化メソッドがここで呼び出される理由を説明すると、まずテキストが設定されると、古いテキストは「dirty」に設定されるため、新しいテキストには「committed」の設定をする必要があります (コミット・フェーズ)。新しいテキストは古いテキストよりも大きい場合もあれば、小さい場合もあるため、境界を測定し直さなければなりません。また、コンポーネントがそのレイアウトを部分的に変更しなければならない場合もあります。例えば、前のラベルでは HTML フォーマットのテキストを表示していたけれども、新しいテキストはプレーンで単純なものであれば、レイアウトもそれに応じて変更しなければなりません。また、テキストが許容サイズに収まりきらない場合には、ラベルを省略してツールチップを設定する必要があるかもしれません。このすべての問題に対処するのが、updateDisplayList() (つまりレイアウト) フェーズです。さらに、この 3 つすべての無効化は、addChild() が呼び出されたときにも自動的に行われます。Flex API には、無効化メソッドがどのように機能するかが詳しく記載されています。以下はその抜粋です。
「複数の無効化メソッドが呼び出されることはめったにありません。通常、コンポーネントにプロパティーを設定すると、対応する無効化メソッドが自動的に呼び出されます」。
ここで覚えておいてほしい重要な点は、Flex で作成されたコンポーネントのほとんどは、そのプロパティーが変更されるたびに適切な無効化メソッドに通知するということです。このことは、既存の Flex コントロールを拡張している限り、当てはまります。その一方、独自の Flex コントロールを一から作成する場合には、適切なタイミングで無効化メソッドを呼び出す必要があることを忘れないでください。
順序の重要性
これらのフェーズが呼び出される順序から、フェーズを呼び出してはならないタイミングについても判断することができます。例えば、開発者がラベルを継承しているときに、誤って updateDisplayList() メソッドにテキスト・プロパティーを設定してしまったとします。この場合、無残な結果になる恐れがあります。テキスト・プロパティーを設定すると validateProperties() が呼び出され、それによって commitProperties()、invalidateDisplayList() が呼びされ、最終的には validateDisplayList() と updateDisplayList() が呼び出されることになります。その結果、無限の再帰ループとなり、Flash Player がハングアップしてアプリケーション全体がフリーズするだけでなく、ユーザーのシステムもフリーズさせてしまう可能性もあるため、フェーズの境界線を超えないように注意してください。幸い、以下の 3 つのルールに従うことで、コンポーネントがフェーズの境界線を確実に順守できるようになります。
- コミット・フェーズでは、サイズと位置決めに関連するプロパティーを除くすべてのプロパティーを処理すること。これには、スタイル、境界レイアウト、角の丸み、テキスト、フォント、色などの設定などが含まれます。invalidateProperties() を再度呼び出す可能性のあるプロパティーは、決して変更しないでください。
- 測定フェーズでは、サイズに関連するプロパティーだけを処理すること。invalidateSize() を再度呼び出す可能性のあるプロパティーは、決して変更しないでください。
- レイアウト・フェーズでは、位置決め、パディング、移動、ベクター描画、ビットマップ塗りつぶしなどを処理すること。invalidateDisplayList() を再度呼び出す可能性のあるプロパティーは、決して変更しないでください。
特別な注意を要する validateNow()
validateNow() もまた、特別な注意が必要なメソッドです。ときには、コンポーネントを作成するときに、そのサイズをすぐに測定しなければならない場合があります。その時点でコンポーネントはメイン表示リスト (ステージ) にはまだ追加されていないため、LayoutManager が測定を行う機会はありません。例えば、画像をキャンバスの右上隅の最端に配置しなければならないというシナリオを考えてみてください。その場合、画像インスタンスを作成し、それを右隅に移動してから、Image.move(canvas.width-image.width,canvas.y); のようなコードを使用したとします。
このコード・スニペットは機能しません。なぜなら画像は作成されたばかりで、まだ測定されていないため、image.width がゼロだからです。image.move() を呼び出す前に image.validateNow() を呼び出すと、Flex は LayoutManager を強制的に起動し、3 つすべてのフェーズを実行するように促します。この 3 つのフェーズに含まれる測定フェーズで画像が正しく測定されて、幅と高さに有効な値が割り当てられます。便利なことに、Flex キャンバスには制約ベースのレイアウトというものが組み込まれていて、このレイアウトでは画像に例えば 'right=0' のような制約を指定することができます。この命令は基本的に、画像を Flex のキャンバスにレイアウトするときには常にキャンバスの右端からの距離をゼロにしなければならないという制約を意味します。そこで、画像をキャンバスの右上隅の最端に配置するには、画像に right=0 と top=0 という制約を指定すればよいわけです。注意する点として、validateNow() はリソースをかなり消費するので、このメソッドを使用するもっともな理由がない限り、呼び出さないようにしてください。
コンポーネントを一から作成する
これまで Flex を使用することによってもたらされるメリットと、Flex を使用するのにふさわしいケース、Flex コンポーネントの操作および拡張方法を学び、Flex コンポーネントのライフサイクルを詳細に探ってきました。最後のステップは、今まで説明したレンダリング機能を、一から作成するカスタム・コンポーネントで実際に使用することです。この後、必要なステップを順に辿りながら、組織の階層を表す組織図を作成する方法を説明します。このサンプル・アプリケーションには、ダミー・データを使用することに注意してください。
ノードの作成
最初のステップは、組織図のノードを作成することです。図 3 のように、ノードの見栄えは背景画像を追加することによって良くすることができます。このような画像を作成するのにどの画像エディターを使用するかは重要ではないので、自分自身またはグラフィック・デザイナーが使い慣れた画像エディターを使ってください。
図 3. 背景画像の追加
作成するノード・クラスは、Node.as という名前にします。リスト 12 でノード要素を作成し、図 3 の画像を使用してノードの背景を作成します。
リスト 12. ノード要素の作成
override protected function updateDisplayList(
unscaledWidth:Number, unscaledHeight:Number): void {
graphics.clear();
var bitmapData : BitmapData = image.content[
"bitmapData"] as BitmapData;
graphics.beginBitmapFill(bitmapData);
graphics.drawRect(0,0,bitmapData.width,bitmapData.height);
graphics.endFill();
graphics.beginFill(0xff0000,.3);
graphics.drawRoundRect(-3,3,bitmapData.width+6,
bitmapData.height+6,10,10);
graphics.drawRoundRect(0,0,bitmapData.width,
bitmapData.height,10,10);
graphics.endFill();
|
graphics.beginBitmapFill() はノード要素の背景としてビットマップによる塗りつぶしを作成するメソッドです。図 4 に、Flex がこのコンポーネントをどのようにレンダリングするかを示します。
図 4. Flex によってレンダリングされた要素ノード
リスト 13 に、ロールオーバー効果をコンポーネントに追加する方法を示します。この追加により、ユーザーがコンポーネントにマウスを重ねると、「フォーカス」がこのコンポーネントを選択していることを示すようになります。
リスト 13. ロールオーバー効果の追加
public class Node extendsUIComponent {
private var rollOver : Boolean;
public function Node() {
addEventListener(MouseEvent.ROLL_OVER,
function(e : Event) : void {
rollOver = true;
invalidateDisplayList();
});
addEventListener(MouseEvent.ROLL_OUT,
function(e : Event) : void {
rollOver = false;
invalidateDisplayList();
});
}
override protected function
updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number): void {
if(rollOver) {
graphics.beginFill(0x0000ff,.2);
graphics.drawRoundRect(
-10,-10,bitmapData.width+20,
bitmapData.height+20,10,10);
graphics.drawRoundRect(
-3,-3,bitmapData.width+6,
bitmapData.height+6,10,10);
graphics.endFill();
}
}
}
|
このコードでは、ブール変数のロールオーバーを定義し、ROLL_OVER および ROLL_OUT を対象としたイベント・リスナーを追加しています。前者の場合はロールオーバーを true に設定し、後者の場合は false に設定します。いずれの場合にしても、表示リストを無効にしていることに注意してください。これは、Flex にボックス周囲のフォーカス枠をレンダリング (または削除) させるためです。
次に、このアプリケーションにテキストを追加します。このステップでは、ノードの内側にラベルを定義し、そのラベルを子として追加するために createChildren() メソッドをオーバーライドします。Node クラス、そして commitProperties() に nodeName 属性を定義した上で、ラベルのテキスト・フィールドに nodeName を入力します。続いて measure() メソッドをオーバーライドし、テキスト行メトリックを使ってテキストの height と width を測定します。さらに、ここでは Node コンポーネントも測定して、その measuredWidth と measuredHeight が組み込み画像に含まれる bitmapData と同じになるように設定します。リスト 14 で、アプリケーションにテキストを追加する方法を説明します。
リスト 14. Flex アプリケーションへのテキストの追加
private var _nodeName : String;
private function set nodeName(
nodeName : String) : void {
_nodeName = nodeName;
invalidateProperties();
}
override protected function createChildren():void {
text = new Text();
addChild(text);
}
override protected function commitProperties(): void {
super.commitProperties();>
text.text = nodeName;
}
override protected function measure(): void {
super.measure();
var metrics : TextLineMetrics = measureText(nodeName);
text.setActualSize(metrics.width+10,metrics.height+5);
measuredHeight = image.height;
measuredWidth = image.width;
}
|
nodeName を設定すると同時にプロパティーを無効にしている点に注意してください。このようにしている理由は、LayoutManager に対し、プロパティーが変更されたのでコミットを呼び出す段階であると通知するためです。ラベルの実際のテキストは、コミット・フェーズの間に commitProperties() メソッドを使用して設定します。最後にノード要素をメイン・アプリケーション・ファイルに追加する方法は、リスト 15 のとおりです。
リスト 15. メイン・ファイルへのノードの追加
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
xmlns:components="article1.components.*">
<components:Node nodeName="John Michael"/>
</mx:Application>
|
図 5 に、ロールオーバー・フォーカス枠を備えた Flex コントロールである、ノード要素の完成バージョンを示します。
図 5. 完成したノード要素
リンクの作成
作業は完了間近です。最後のステップの一環として、ノードに接続するためのリンクを作成します。リンクを作成するのは、ノードを作成するより遙かに簡単です。リスト 16 に、UIComponent を継承し、updateDisplayList() をオーバーライドし、そしてリンク・コンポーネントの graphics オブジェクトを使用して線を作成する方法を記載します。
リスト 16. リンクの作成
package article1.components {
import mx.core.UIComponent;
public class Link extends UIComponent {
public var fromNode : Node;
public var toNode : Node;
override protected function updateDisplayList
(unscaledWidth:Number,
unscaledHeight:Number): void {
graphics.clear();
graphics.lineStyle(4,0x0000ff,.5);
graphics.moveTo(fromNode.x+fromNode.width/2,
fromNode.y+fromNode.height/2);
graphics.lineTo(toNode.x+toNode.width/2,
toNode.y+toNode.height/2);
}
}
}
|
ここでは純粋なベクター・ベースの描画を作成し、ソース・ノードと宛先ノードを結ぶ線をレンダリングしています。これらのノードとリンクを組み立てて、組織図を作成する方法はリスト 17 のとおりです。
リスト 17. すべての要素の統合
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" xmlns:components="article1.components.* ">
<components:Link fromNode="{node1}"
toNode="{node2}"/>
<components:Link fromNode="{node2}"
toNode="{node3}"/>
<components:Link fromNode="{node1}"
toNode="{node3}"/>
<components:Node id="node1"
nodeName="John Michael"
x="400" y="300"/>
<components:Node id="node2"
nodeName="Harris Scott"
x="700" y="600"/>
<components:Node id="node3"
nodeName="Paul Simpson"
x="100" y="600"/>
</mx:Application>
|
最終的に仕上がった Flex によるカスタムの組織図表示アプリケーションを図 6 に示します。
図 6. 最終的な UI
この記事で説明したカスタム Flex コンポーネントのソース一式は、「ダウンロード」セクションからダウンロードすることができます。
現時点で、3 つのノードを持つ組織図のコンポーネントが完成しました。この組織図にさらにノードとリンクを追加するには、必要なデータを用意すればよいだけの話です。Flex レンダリング・エンジン自体には関係しないので、後の作業は読者にお任せします。この種のコンポーネントは、組織図で目にするような相互関係や、ルーター・リンク・トポロジー、そして同様のアプリケーションを表現するために使用することができます。このようなアプリケーションを作成する必要がないとしても、この記事で説明した手法とサンプルを利用すれば、特定のビジネス・ニーズに対応する拡張可能なカスタム Flex コンポーネントを作成することができます。
次のステップ
この記事では、組み込み Flex コンポーネントの拡張方法、ならびに独自の Flex コンポーネントを一から作成する方法を含め、Flex のレンダリング・エンジンをある程度まで詳しく説明しました。しかし Flex には、詳細を調べて理解すべき内容はまだまだ残っています。例えば、レイアウト・フェーズは Flex コンポーネントのライフサイクルのなかで重要な部分となっていますが、初期化フェーズや作成フェーズなどのフェーズも同じく重要です。Flex が提供する内容についての概要を学ぶには、Flex 言語リファレンス (Flex Language Reference、「参考文献」を参照) が絶好の情報源となります。また、高度な RIA を構築するとなったら、イベント・ディスパッチ・モデルについて理解しておくことも重要です。さらに、大抵はソース・コードと併せて見事な Flex コンポーネントを紹介しているブログや Web サイトも豊富にあるので調べてみてください。そのうちのいくつかは、「参考文献」セクションに記載しています。
ダウンロード | 内容 | ファイル名 | サイズ | ダウンロード形式 |
|---|
| sample code | sample.zip | 575KB | HTTP |
|---|
参考文献
著者について  | 
|  | Sandeep Malik は IBM Cognos NOW! の技術リーダーであり、インドの Pune にある India Software Labs に勤務しています。彼は Cognos NOW! のための新世代 UI の設計やアーキテクチャーのフェーズに、また OBI (Operation Business Intelligence) エンジンのメモリーやリアルタイム・ストリーミングの業務に従事してきています。彼は本格的なグラフィックスやチャート・ライブラリー、クライアントサイド・ストリーミング、ノンブロッキング I/O、そして非同期システム全般を幅広く経験してきています。IBM に入社する前にはネットワーク・セキュリティーの分野で働いており、ネットワーク・トラフィックの分布を変化させるパターン (ボットネット、ワーム・スキャンなど) の分析をしていました。また以前勤務していた会社の 1 つでサーブレット 2.4 仕様の実装にも従事していました。彼は時間のあるときにはクリケット観戦を楽しみ、生まれ変わったら自分自身もクリケット選手になりたいと思っています。 |
記事の評価
|