asでカスタムコンポーネント(1)

なんか、昔書いたカスタムコンポーネントのエントリにトラックバックがいつの間にか張られていたり。
別に悪い気はしないけど、当時自分も勘違いしていたこととかあってこのまま置いとくのは恥ずかしくなってきたので、何回かに分けてすこし丁寧に解説してみようと思います。いえ、ネタがないからではなくて。

カスタムコンポーネントMXMLでも充分出来ます。速度もそこそこに速いし、デザインの修正もしやすいので、無理にASで書く必要はありません。
それでもASでコーディングしたいという場合、
・動的なコンポーネントの追加がしたいけどRepeaterとか超ウザい
・テンプレートとして使える便利なコンポーネントを作っておきたい
インターフェイスMXMLだとなんだかワケわからない
バインディングしたいプロパティがモデルオブジェクトの奥の階層にあってメンドイ
・状態をStateで入れ替えたいけどステート関連のMXMLタグみてると腹が立ってくる
などの理由があるかと思います。

そこでカスタムコンポーネントを作るためにASに足を突っ込むわけですが、UIComponentを中心とするコンポーネントモデルの構造はとても複雑で、わかりやすいといわれるFlexのマニュアルを見ていても実際やってみるとワケわからない事態に陥ります。無効化とか言われても今すぐ書き換えるのかどこまで書き換えるのかどこでどのメソッドに飛ぶのか・・・。

とりあえず、UIComponentを中心に、採用されている簡単な概念をコトバで並べ立てていこうと思います。
サンプルコードを書かないのはこれが自宅の32インチ液晶テレビに接続されたゲーム専用機で入力されているからで、要約すると打つのがメンドくてコードを検証する環境もないからです。

  • UIComponentの目的

1.子コンポーネントの再描画コストをなるべく減らす

結構多くの人が、UIComponentは重いからShapeを使ってる、と口にします。さらに子を持たせたいときにはSpriteでガマンします、とかいいます。
それはそれで有効な場面も多いのですが、SpriteとUIComponentの違いのひとつに「無効化」という技術があることは知っておいたほうがよいと思います。

無効化とは、Flashが次のフレーム(描画のタイミング)に入るときまで一切の描画作業をさせない機能です。
たとえば、複数のTextFieldを子としてもつコンポーネントを考えてみましょう。
それぞれのtextはバインディングしていて、違うタイミングで更新されます。
でも同じ親コンポーネントに入っているので、ひとつのテキストを書き換えてもコンポーネント全体が再描画されてしまいます。
コンポーネントにスキンなんかを適用したら、それこそ何回再描画するのか・・・ということになります。

そこで、UIComponentでは、普段はともかくEvent.RENDERを受信したらその時点の情報で再描画!という動きをさせています。
一つ一つのプロパティに変更が加わった時点では、invalidateProperties()を実行して、コンポーネント内のどれかのプロパティに変更が加わった、というフラグだけを立てます。
すると、RENDERイベントが来た際にcommitProperties()を実行して再描画に必要な情報の計算を行い、再描画の必要があるときにはinvalidateDisplayList()を呼び出して再描画フラグを立てます。
再描画フラグが立っていた場合には、引き続いてupdateDisplayList()を実行して、子コンポーネントの再描画や大きさ・位置の変更を行います。

つまり、プロパティが何も変更されていなければ再描画はおこなわれませんし、commitProperties()が実行されても再描画フラグが立たなければupdateDisplayListは実行されません。プロパティの変更なしで再描画だけ行わせたければinvalidateDisplayList(),サイズ変更だけ行いたいならinvalidateSize()を呼び出すだけです。
validateNow()はフレームの進行を無視していきなり再描画を行います(その結果、コンポーネントの各種プロパティが次のフレームを待たずに変更されます)。

このように、メソッドの数こそ多いですが、実行される描画命令はSpriteに比べてかなり削減されるので、さほど遜色のない実行速度を保つことが出来ます。
カスタムコンポーネントでは、基本的にcommitProperties()とupdateDisplayList()をオーバーライドして、自分のロジックを実装するわけですが、この二つのメソッドの役割分担をどう設計するかで「パターンに沿ったコーディング」が出来るかどうかが決まってきます。

2.大きさや見た目をあらわすプロパティの扱いを統一する
Shapeが、自分の大きさを示すwidthとheightについて、描画後にしか取得できないということをご存知でしょうか。
要するに「書かれた絵の大きさ」でしかないわけですが、UIComponent(正しくはInteractiveObjectかな?)では、大きさに関してもっと多くのプロパティをもっています。
横幅ひとつ取ってみても、width,explicitWidth,measuredWidth,maxWidth,minWidth,measuredMinWidth・・・などときりがない感じです。
説明すると、
・explicit 「明示的な」MXMLでwidthを明示したりするとこちらに入ります。
・measured 「仮の」MXMLで指定されなかった場合などに、子のコンポーネントやcommitProperties()で計算した結果などを元にmeasure()メソッドで計算します。
・min,max 「最小、最大」これにもexplicit,measuredが存在します。
これらの大きさのプロパティは、コンポーネントの大きさを自動で計算するか、明示的に外部から指定するか、あるいは外部のデータにリンクするか、といったようなさまざまなケースに対応して描画できるようにするためにつけられています。
これらの一部はFlexShapeやFlexSprite、UITextFieldといったクラスにもつけられていますが、これらのクラスは「外部からぽんと置くだけで大きさまで指定できるように」用意されているということですね。

・scale9grid
これはとても面白い機能です。scaleを真ん中の区画だけに効かせる事が出来ます。
・matrix
UIComponent以外にもありますが、座標情報にたいして外部的に手を入れられるので変形に便利です。
行列計算ができないと使えないように見られがちですが、大丈夫。ボクでも使えました。

他にもフィルターやスキン、CSS、ステートなどが使えるインターフェイスを備えています。

3.アクセシビリティの実現
 具体的には、フォーカス、キーボードイベント、ドラッグ&ドロップ、タブ制御などです。
 Shape単位から自分で実装しようと思うと気が遠くなります。

4.テキストの扱いの統一化
 FlashではTextの描画方法が他のShapeと異なることはご存知でしょうか。
 埋め込みフォントはビットマップ描画ですが、非埋め込みフォントについてはOSの利用フォントから直に取っています。
 テキストを含むコンポーネントを回転させると消えてしまうのはこのせいです。
 TextFieldオブジェクトは生成に関して負担が大きいようで、フレームワークのあちこちで大切に再利用されています。
 時にはListItemRendererのコードなどを見て、過保護ぶりに涙するのもよいでしょう。

こんな感じで、とにかくやたらとメソッドやプロパティが多いですが、一つ一つはどれも価値のあるものばかりです。

次回は、UIComponentの基本メソッドをもう少し掘り下げてみてみようと思います。