- 2011/12/1002:48
前回の記事の最後で書いたように、IconItemRenderer
クラスを拡張して、Flex/AIR モバイルアプリ用に最適化かつ、進捗グラフ(プログレスバー)表示機能を追加した、リスト用カスタムアイテムレンダラークラスを実装してみました。
まずは、どんなものを作成したのか、結果から。
ProgressBarIconItemRenderer (ver 1.0.0)
[概要]
Adobe Flex 4.5 SDK / Adobe AIR 2.5 SDK
で新しく加わった IconItemRenderer
(ASDoc) クラスを拡張して、進捗バーも合わせて表示をする、モバイル端末用に最適化されたアイテムレンダラーです。
表示例・使用例
表示例・使用例は、以下のとおりです。
この表示例・使用例でのソースコードは以下のとおりです。
- アイテムレンダラー
- ビューコンポーネント
(Project)/src/renderers/PgBarItemRenderer.mxml
<?xml version="1.0" encoding="utf-8"?> <renderers:ProgressBarIconItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:renderers="renderers.*" labelField="projectName" messageField="projectDescription" barLengthField="projectWeight" progressValueField="projectProgress" iconField="image" iconWidth="128" iconHeight="128" decorator="@Embed('./assets/images/arrow.png')" defaultBarLength="100"> </renderers:ProgressBarIconItemRenderer>
(Project)/src/views/SampleView.mxml
<!-- ...(前略)... --> <fx:Declarations> <s:ArrayCollection id="arrayColl"> <fx:Object projectName="Project1" projectDescription="HogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHogeHoge" projectWeight="160" projectProgress="30" image="./assets/images/active.png" /> <fx:Object projectName="Project2" projectDescription="PiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyoPiyo" projectWeight="240" projectProgress="80" image="./assets/images/active.png" /> <fx:Object projectName="Project3" projectDescription="FugaFugaFugaFugaFugaFugaFuga" projectWeight="320" projectProgress="95" image="./assets/images/active.png" /> <fx:Object projectName="Project4" projectDescription="PiyoPiyoPiyoPiyoPiyo" projectWeight="190" projectProgress="20" image="./assets/images/active.png" /> <fx:Object projectName="Project5" projectDescription="HomuHomu" projectWeight="160" projectProgress="100" image="./assets/images/done.png" /> <fx:Object projectName="Project6" projectDescription="FugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFuga" projectWeight="240" projectProgress="80" image="./assets/images/active.png" /> <fx:Object projectName="Project7" projectDescription="Hoge" projectWeight="320" projectProgress="100" image="./assets/images/active.png" /> <fx:Object projectName="Project8" projectDescription="FugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFugaFuga" projectWeight="190" projectProgress="100" image="./assets/images/done.png" /> <fx:Object projectName="Project9" projectDescription="PiyoPiyo" projectWeight="160" projectProgress="30" image="./assets/images/active.png" /> <fx:Object projectName="Project10" projectDescription="Hoge" projectWeight="320" /> <fx:Object projectName="Project11" projectDescription="Fuga" projectProgress="20" /> <fx:Object projectName="Project12" projectDescription="Piyo" /> <fx:Object projectName="Project13" /> </s:ArrayCollection> </fx:Declarations> <s:Group> <s:List id="myList" dataProvider="{arrayColl}" itemRenderer="renderers.PgBarItemRenderer" width="100%" height="100%" borderVisible="true" borderColor="0xB3B3B3" /> <!-- ...(後略)... -->
[Q&A]
Q1. 端末を縦向きから横向きに変えても、ちゃんと動作しますか?
A1. もちろん、端末を縦向き(portrait)から横向き(landscape)にしても、その逆にしても、コンポーネントの大きさが再計算されて、再描画されるので、レイアウトを崩すことなく描画範囲を自動的に変更して、問題なく表示されます (参考:下図)。
Q2. 進捗率のテキストの位置を進捗バーの右端揃えにしたいのですが?
A2. CSS (スタイルシート) を使って指定できます。
同梱のデフォルトCSSファイル (defaults.css
) の .progressBarIconItemRendererProgressValueTextStyle
クラスセレクタ内の textAlign
スタイルプロパティの値を right
にすれば、進捗率のテキストの位置をそれぞれの項目の進捗バーの長さに応じて、右端揃えになります (参考:下図)。
(Project)/src/assets/styles/defaults.css
.progressBarIconItemRendererProgressValueTextStyle { fontSize: 16; color: #666666; textAlign: right; }
Q3. 表示する進捗バーの長さを指定しなかった場合はどうなるのですか?
A3. カスタムアイテムレンダラーMXMLファイル内の ProgressBarIconItemRenderer.defaultBarLength
に指定した値が使われます。
さらに、 ProgressBarIconItemRenderer.defaultBarLength
に何も指定しなかった場合は、デフォルト値として、アプリケーションDPI値(端末の解像度)に応じて 160 / 240 / 320 の値のいずれかが用いられます。
さらに、 ProgressBarIconItemRenderer.defaultBarLength
をアイテムレンダラーとして適用した s:List
コンポーネントの width
が、 (進捗バーの長さ) + (アイコン画像の横幅) + (デコレータ画像の横幅) + (paddingやgapなどの空白の幅の総和) の和よりも小さく設定されている場合は、コンポーネントの表示領域からはみ出さないように、ラベルテキストの横幅やメッセージテキストの横幅と同様に進捗バーの長さも自動調節されるようになっています。
このように、横幅の長さにおける不具合は起きないようにしてあります。
Q4. 表示する進捗バーの長さやデフォルトの長さを、べらぼうに大きくしたらヤバイですか?
A4. もしも、 barLength
が 2560 (つまり、進捗バーの長さが 2560 px) というデータが結び付けられていたとしても、 s:List
コンポーネントの width
値を超えてしまわないように、進捗バーの長さが自動的に調整されます。その際、横の長さとして取り得る最大の長さが適用されます。
なお、 s:List
コンポーネントの width
値が設定されていない場合は、端末の解像度にあわせて、おおよその長さに当たりをあてて進捗バーの長さを自動的に調整します。様々なデバイスで確認したわけではありませんが、横幅が設定されていないListコンポーネントの右端が切れてしまわないように、おおむね描画が自動的に再調整されるようにしています。やや神経質なくらいの大きさ(つまりややちっさい横幅)に再調整されてしまうので、Listコンポーネントの width
値には、端末の画面の横幅いっぱい (つまり、 100%
) を設定しておくのが良いでしょう。
Q5. 各項目の縦幅は自動的に調整されますか?
A5. はい。各項目内のメッセージテキストのテキスト量に応じて、縦幅が自動的に調整されます。したがって、項目ごとに縦幅が異なるリストを表示することができるため、余分な垂直方向の空白が描写されることがなく、とても効率的なリスト表示が可能です。
Q6. 進捗率のテキスト表示はOFFにできますか?
A6. 進捗率テキストの表示/非表示は切り替えることが可能です。
ProgressBarIconItemRenderer
クラスの showProgressValueText
プロパティを false
にすれば、進捗率テキストを非表示にすることができます。
なお、進捗率テキストを非表示にした場合は、そのテキスト表示部分の縦幅が切り詰められるように、コンポーネントの縦幅が自動的に再計算されて、再描画されます (実行後にイベント等で動的に表示/非表示を切り替えた際の動作は、未確認)。このため、余分な垂直方向の空白が描写されることがなく、とても効率的なリスト表示が可能です。
Q7. 進捗バーの色は変更できるのですか?
A7. CSS (スタイルシート) を使って指定できます。
ProgressBarIconItemRenderer
の progressBarBackgroundColor
, progressBarDoneColor
, progressBarBorderColor
スタイルプロパティの値を変更すれば、進捗バーのカラーをお好きなものに変更できます (参考:下図)。
(Project)/src/assets/styles/customProgressBarColorStyle.css
@namespace renderers "renderers.*"; renderers|ProgressBarIconItemRenderer { progressBarBackgroundColor: #FFFFFF; progressBarDoneColor: #999900; progressBarBorderColor: #999999; }
Q8. 既知のバグはありますか?
A8. 描画に関するバグが 1件見つかっています・・・。
このProgressBarIconItemRenderer
をアイテムレンダラーに適用した s:List
コンポーネントの縦幅 (height
) に何も値を設定しない自動計測状態で横幅 (width
) に 99%
以上の値を設定すると、初回の描画で、なぜかリストコンポーネントの縦幅 (height
) が実測値よりも大きくなってしまい、アイテムの最後尾の下に余分な余白が発生してしまうことがあります。端末を回転させたりして再描画させてやると、なぜか直ります。そして、再現性もいまいち掴めていません。
[作り方]
今回、私が実装してみた ProgressBarIconItemRenderer
のソースコードを下に載せつつ、作成の説明をします。
ソースコード
ただただ、長いです(1000行超え)。
ソースコード内に、たくさんドキュメントとコメントをつけました。
- アイテムレンダラークラス(プログラム)
- 同梱するデフォルトスタイルシート
(Project)/src/renderers/ProgressBarIconItemRenderer.as
/*############################################################## ProgressBarIconItemRenderer Class (ver 1.0.0) ##############################################################*/ package renderers { import flash.display.Sprite; import mx.core.mx_internal; import mx.styles.CSSStyleDeclaration; import spark.components.IconItemRenderer; import spark.components.supportClasses.StyleableTextField; import spark.core.ContentCache; import spark.core.IContentLoader; import spark.core.IGraphicElementContainer; import spark.core.ISharedDisplayObject; use namespace mx_internal; //========================================================== // // スタイル // //========================================================== //__________________________________________________________ // ギャップ /** * 水平方向のギャップ. */ [Style(name="horizontalGap", type="Number", format="Length", inherit="no")] /** * 垂直方向のギャップ. */ [Style(name="verticalGap", type="Number", format="Length", inherit="no")] //__________________________________________________________ // 進捗バーの色 /** * 進捗バーの未進捗部分の塗りつぶしの色です. * * @default 0x990000 */ [Style(name="progressBarBackgroundColor", type="uint", format="Color", inherit="yes")] /** * 進捗バーの既進捗部分の塗りつぶしの色です. * * @default 0x009900 */ [Style(name="progressBarDoneColor", type="uint", format="Color", inherit="yes")] /** * 進捗バーの枠線の色です. * * @default 0x999999 */ [Style(name="progressBarBorderColor", type="uint", format="Color", inherit="yes")] //__________________________________________________________ // 進捗率の表記 /** * 進捗率テキストコンポーネントのスタイルに使用する, * CSS スタイル宣言の名前です. * * @default progressBarIconItemRendererProgressValueTextStyle */ [Style(name="progressValueTextStyleName", type="String", inherit="no")] //========================================================== // // クラス // //========================================================== /** * <code>ProgressBarIconItemRenderer</code> クラスは, * モバイルデバイス用に最適化されたパフォーマンスの良い, * アイテムレンダラーです. 進捗状況を, * バー表示でわかりやすく表示します. * * <p>リストベースのコントロールの各アイテムに対して, * 次の 5 個のオプションパーツが表示されます.</p> * * <ul> * <li><code>iconField</code> または <code>iconFunction</code> * プロパティによって定義される, 左側のアイコン.</li> * <li><code>labelField</code> または <code>labelFunction</code> * プロパティによって定義される, アイコンの横の 1 行テキスト.</li> * <li><code>messageField</code> または <code>messageFunction</code> * プロパティによって定義される, 進捗バーの下の複数行のメッセージ.</li> * <li><code>decorator</code> プロパティによって定義される, * 右側のデコレーターアイコン.</li> * <li><code>barlengthField</code> および <code>progressValueField</code> * プロパティによって定義される, テキストラベルの下の進捗バーと進捗率テキスト.</li> * </ul> * * <p>1 行のテキストラベルにフォントサイズや色などの CSS スタイルを適用するには, * <code>ProgressBarIconItemRenderer</code> クラスに CSS スタイルを設定します. <br /> * 複数行のメッセージにスタイルを設定するには, * <code>messageStyleName</code> スタイルプロパティを使用します. <br /> * 進捗率テキストに CSS スタイルを適用するには, * <code>progressValueTextStyleName</code> スタイルプロパティを使用します.</p> * * @mxml * * <p>The <code><renderers:IconItemRenderer></code> tag inherits all of the tag * attributes of its superclass and adds the following tag attributes:</p> * * <pre> * <renderers:ProgressBarIconItemRenderer * <strong>Properties</strong> * barlengthField="null" * barlengthFunction="null" * decorator="" * defaultBarLength="160" * iconContentLoader="<i>See property description</i>" * iconField="null" * iconFillMode="scale" * iconFunction="null" * iconHeight="NaN" * iconPlaceholder="null" * iconScaleMode="stretch" * iconWidth="NaN" * label="" * labelField="null" * labelFunction="null" * messageField="null" * messageFunction="null" * progressValueField="null" * progressValueFunction="null" * showProgressValueText="true" * * <strong>Common Styles</strong> * horizontalGap="8" * iconDelay="500" * messageStyleName="iconItemRendererMessageStyle" * progressBarBackgroundColor: #990000 * progressBarDoneColor: #009900 * progressBarBorderColor: #999999 * progressValueTextStyleName: "progressBarIconItemRendererProgressValueTextStyle" * verticalGap="6" * /> * </pre> * * @langversion 3.0 * @playerversion AIR 2.5 * @productversion Flex 4.5 * * @author mio * */ public class ProgressBarIconItemRenderer extends IconItemRenderer { //========================================================== // // クラスプロパティ : プライベート // //========================================================== //__________________________________________________________ // const /** * @private * * 進捗バーの未進捗部分の塗りつぶしの色のデフォルト値. */ private static const DEFAULT_PGBAR_BG_FILL_COLOR : uint = 0x990000; /** * @private * * 進捗バーの既進捗部分の塗りつぶしの色のデフォルト値. */ private static const DEFAULT_PGBAR_FG_FILL_COLOR : uint = 0x009900; /** * @private * * 進捗バーの枠線の色のデフォルト値. */ private static const DEFAULT_PGBAR_BORDER_COLOR : uint = 0x999999; /** * @private * * 進捗バーの枠線の太さ. */ private static const PGBAR_BORDER_THICKNESS : Number = 1.2; /** * @private * * 進捗バーの高さ. */ private static const PGBAR_HEIGHT : Number = 6; /** * @private * * 進捗バーの角の丸み. */ private static const PGBAR_ELLIPSE : Number = 3; //__________________________________________________________ // variable /** * @private * * 静的アイコンイメージキャッシュ. */ mx_internal static var _imageCache : ContentCache; //========================================================== // // コンストラクタ // //========================================================== /** * コンストラクタ. */ public function ProgressBarIconItemRenderer() { // スーパークラスのコンストラクタを呼び出し super(); // イメージキャッシュの有効化 if (_imageCache == null) { _imageCache = new ContentCache(); _imageCache.enableCaching = true; _imageCache.maxCacheEntries = 128; } // デフォルトの進捗バーの全体の長さを算出 _defaultBarLength = oldUnscaledWidth / 2; } //========================================================== // // インスタンスプロパティ : プライベート // //========================================================== /** * 進捗バーの全体の長さ. */ mx_internal var barLength : Number = 0; /** * 進捗率の値. */ mx_internal var progressValue : Number = 0; /** * @private * * 進捗バーの前面描写. */ private var bar : Sprite; /** * @private * * 進捗バーの背面描写. */ private var barback : Sprite; /** * @private * * 進捗バーの全体の長さの修正後の値. */ private var barLengthEstimated : Number = -1; //========================================================== // // インスタンスプロパティ : オーバーライド // //========================================================== //__________________________________________________________ // データやラベルの変更に応答するための setter /** * @private * * データの変更に応答するには, * この setter をオーバーライドします. * * (Auto Generated method stub) */ override public function set data(value : Object) : void { super.data = value; // the data has changed. push these changes down in to the // subcomponents here. invalidateProperties(); } /** * @private * * ラベルの変更に応答するには, * この setter をオーバーライドします. * * (Auto Generated method stub) */ override public function set label(value : String) : void { if (value == label) return; super.label = value; // the label has changed. push these changes down in to the // subcomponents here. invalidateProperties(); } //__________________________________________________________ // オプションのカスタムイメージローダー /** * @private */ private var _iconContentLoader : IContentLoader = _imageCache; /** * コンテンツローダークライアントに関連付ける * イメージキャッシュやキューなどの * オプションのカスタムイメージローダーです. * * <p>デフォルト値は, <code>IconItemRenderer</code> * で定義されている静的コンテンツキャッシュで, * 128 エントリまで可能です. </p> */ override public function get iconContentLoader() : IContentLoader { return _iconContentLoader; } /** * @private */ override public function set iconContentLoader(value : IContentLoader) : void { if (value == _iconContentLoader) return; _iconContentLoader = value; if (iconDisplay) iconDisplay.contentLoader = _iconContentLoader; } //========================================================== // // インスタンスプロパティ : パブリック // //========================================================== //__________________________________________________________ // 進捗バーの全体の長さのデフォルト値関連 private var _defaultBarLength : Number = 0; /** * 進捗バーの全体の長さのデフォルト値を指定します. * * <p>デフォルト値は, アプリケーションDPI値によって変わります.</p> * <table> * <tr><th><code>applicationDPI</code></th><th><code>defaultBarLength</code></th></tr> * <tr><td><code>DPIClassification.DPI_160</code></td><td>160</td></tr> * <tr><td><code>DPIClassification.DPI_240</code></td><td>240</td></tr> * <tr><td><code>DPIClassification.DPI_320</code></td><td>320</td></tr> * </table> * * @default 160 */ public function get defaultBarLength():Number { return _defaultBarLength; } /** * @private */ public function set defaultBarLength(value:Number) : void { if (value == _defaultBarLength) return; _defaultBarLength = value; barLengthFieldOrFunctionChanged = true; barLengthChanged = true; invalidateSize(); invalidateProperties(); } //__________________________________________________________ // 進捗バーの全体の長さ関連 /** * @private */ private var _barLengthField : String = ""; /** * @private */ private var barLengthFieldOrFunctionChanged : Boolean; /** * @private */ private var barLengthChanged : Boolean; /** * 進捗バーの全体の長さを指定するフィールド名です. */ public function get barLengthField() : String { return _barLengthField; } /** * @private */ public function set barLengthField(value : String) : void { if (value == _barLengthField) return; _barLengthField = value; barLengthFieldOrFunctionChanged = true; barLengthChanged = true; invalidateSize(); invalidateProperties(); } /** * @private */ private var _barLengthFunction : Function; /** * 進捗バーの全体の長さを決定する各アイテムで * 実行されるユーザー指定の関数です. * * <p><code>barLengthFunction</code> プロパティは * <code>barLengthField</code> プロパティをオーバーライドします.</p> */ public function get barLengthFunction() : Function { return _barLengthFunction; } /** * @private */ public function set barLengthFunction(value : Function) : void { if (value == _barLengthFunction) return; _barLengthFunction = value; barLengthFieldOrFunctionChanged = true; barLengthChanged = true; invalidateSize(); invalidateProperties(); } //__________________________________________________________ // 進捗バーの進捗率関連 /** * @private */ private var _progressValueField : String = ""; /** * @private */ private var progressValueFieldOrFunctionChanged : Boolean; /** * @private */ private var progressValueChanged : Boolean; /** * 進捗バーの進捗率を指定するフィールド名です. */ public function get progressValueField() : String { return _progressValueField; } /** * @private */ public function set progressValueField(value : String) : void { if (value == _progressValueField) return; _progressValueField = value; progressValueFieldOrFunctionChanged = true; progressValueChanged = true; invalidateProperties(); } /** * @private */ private var _progressValueFunction : Function; /** * 進捗バーの進捗率を決定する各アイテムで * 実行されるユーザー指定の関数です. * * <p><code>progressValueFunction</code> プロパティは * <code>progressValueField</code> プロパティをオーバーライドします.</p> */ public function get progressValueFunction() : Function { return _progressValueFunction; } /** * @private */ public function set progressValueFunction(value : Function) : void { if (value == _progressValueFunction) return; _progressValueFunction = value; progressValueFieldOrFunctionChanged = true; progressValueChanged = true; invalidateProperties(); } //__________________________________________________________ // 進捗率の表記 /** * @private */ private var _showProgressValueText : Boolean = true; /** * 進捗率のテキスト表記をするかどうかを設定します. * * @default true */ [Inspectable(category="Other", enumeration="true,false", defaultValue="true")] public function get showProgressValueText() : Boolean { return _showProgressValueText; } /** * @private */ public function set showProgressValueText(value : Boolean) : void { if (value == _showProgressValueText) return; _showProgressValueText = value; invalidateSize(); invalidateProperties(); } /** * アイテムレンダラーの進捗率データを表示するために * 使用されるテキストコンポーネントです. */ protected var progressValueTextDisplay : StyleableTextField; //========================================================== // // インスタンスメソッド : プロテクテッド // //========================================================== //__________________________________________________________ // 進捗率バーのコンポーネントを作成/破棄するメソッド /** * <code>bar</code> 及び <code>barback</code> コンポーネントを作成します. */ protected function createProgressBar() : void { // コンストラクト barback = new Sprite(); bar = new Sprite(); // 進捗バーを描写 drawProgressbarBackground(); drawProgressbarFourground(); // キャッシュ barback.cacheAsBitmap = true; bar.cacheAsBitmap = true; // 表示リストに吊るす this.addChild(barback); this.addChild(bar); } /** * <code>bar</code> 及び <code>barback</code> コンポーネントを破棄します. */ protected function destroyProgressBar() : void { // 表示リストから削除 this.removeChild(bar); this.removeChild(barback); // デストラクト bar = null; barback = null; } //__________________________________________________________ // 進捗率バーのコンポーネントを作成/破棄するメソッド /** * 進捗バーの背面描写を行います. */ protected function drawProgressbarBackground() : void { // 描写スタイル定義 var fillColor : uint = DEFAULT_PGBAR_BG_FILL_COLOR; var borderColor : uint = DEFAULT_PGBAR_BORDER_COLOR; if (getStyle("progressBarBackgroundColor") != "undefined") fillColor = getStyle("progressBarBackgroundColor"); if (getStyle("progressBarBorderColor") != "undefined") borderColor = getStyle("progressBarBorderColor"); // 描写をクリア barback.graphics.clear(); // 進捗バーの全体の長さを取得 var len : Number = (barLengthEstimated >= 0) ? barLengthEstimated : barLength; // 描写を行う barback.graphics.lineStyle(PGBAR_BORDER_THICKNESS, borderColor); barback.graphics.beginFill(fillColor); barback.graphics.drawRoundRect(0, 0, len, PGBAR_HEIGHT, PGBAR_ELLIPSE, PGBAR_ELLIPSE); barback.graphics.endFill(); } /** * 進捗バーの前面描写を行います. */ protected function drawProgressbarFourground() : void { // 描写スタイル定義 var fillColor : uint = DEFAULT_PGBAR_BG_FILL_COLOR; var borderColor : uint = DEFAULT_PGBAR_BORDER_COLOR; if (getStyle("progressBarDoneColor") != "undefined") fillColor = getStyle("progressBarDoneColor"); if (getStyle("progressBarBorderColor") != "undefined") borderColor = getStyle("progressBarBorderColor"); // 描写をクリア bar.graphics.clear(); // 既進捗バーの長さを計算 var len : Number = (barLengthEstimated >= 0) ? barLengthEstimated : barLength; var progressLength : Number = (progressValue / 100) * len; if (progressLength < 0) progressLength = 0; if (progressLength > len) progressLength = len; // 描写を行う bar.graphics.lineStyle(PGBAR_BORDER_THICKNESS, borderColor, 0); bar.graphics.beginFill(fillColor); bar.graphics.drawRoundRect(0.5, 0, progressLength - 0.5, PGBAR_HEIGHT - 1, PGBAR_ELLIPSE, PGBAR_ELLIPSE); bar.graphics.endFill(); } //__________________________________________________________ // 進捗率のテキスト表記コンポーネントを作成/破棄するメソッド /** * <code>progressValueTextDisplay</code> コンポーネントを作成します. */ protected function createProgressValueTextDisplay() : void { // コンストラクト progressValueTextDisplay = StyleableTextField(createInFontContext(StyleableTextField)); // テキストフィールドの属性 progressValueTextDisplay.styleName = this; progressValueTextDisplay.editable = false; progressValueTextDisplay.selectable = false; progressValueTextDisplay.multiline = false; progressValueTextDisplay.wordWrap = false; // スタイルシートの適用 var progressValueTextStyleName : String = getStyle("progressValueTextStyleName"); if (progressValueTextStyleName) { var styleDeclaration : CSSStyleDeclaration = styleManager.getMergedStyleDeclaration("." + progressValueTextStyleName); if (styleDeclaration) { progressValueTextDisplay.styleDeclaration = styleDeclaration; } } // 表示リストに吊るす this.addChild(progressValueTextDisplay); } /** * <code>progressValueTextDisplay</code> コンポーネントを破棄します. */ protected function destroyProgressValueTextDisplay() : void { if (progressValueTextDisplay) { // 表示リストから削除 this.removeChild(progressValueTextDisplay); // デストラクト progressValueTextDisplay = null; } } //========================================================== // // インスタンスメソッド : オーバーライド // //========================================================== //__________________________________________________________ // アイテムレンダラーの子オブジェクトを作成するメソッド /** * @private * * アイテムレンダラーの子を作成するには, * このメソッドをオーバーライドします. * * (Auto Generated method stub) */ override protected function createChildren() : void { super.createChildren(); // create any additional children for your item renderer here } //__________________________________________________________ // スタイルプロパティの変更を検出するメソッド /** * @private * * スタイルプロパティの変更に応答するには, * このメソッドをオーバーライドします. * * (Auto Generated method stub) */ override public function styleChanged(styleName : String) : void { // スタイルプロパティの変更があるか検出 var allStyles : Boolean = !styleName || styleName == "styleName"; // スーパークラスのスタイルプロパティの変更を検出 super.styleChanged(styleName); // 進捗率テキストコンポーネントのスタイルシートの適用 if (allStyles || styleName == "progressValueTextStyleName") { if (_showProgressValueText && progressValueTextDisplay) { var progressValueTextStyleName : String = getStyle("progressValueTextStyleName"); if (progressValueTextStyleName) { var styleDeclaration : CSSStyleDeclaration = styleManager.getMergedStyleDeclaration("." + progressValueTextStyleName); if (styleDeclaration) { progressValueTextDisplay.styleDeclaration = styleDeclaration; progressValueTextDisplay.styleChanged("styleName"); } } } } } //__________________________________________________________ // コンポーネントに設定されたプロパティを処理するメソッド /** * @private * * コンポーネントに設定されたプロパティを処理するには, * このメソッドをオーバーライドします. * * (Auto Generated method stub) */ override protected function commitProperties():void { // スーパークラスの commitProperties() メソッドを呼び出し super.commitProperties(); // TODO _showProgressValueText がイベントなどで変更された時の処理を書く /// メモ: progressValueField の生成・破棄まで面倒をみるかどうかが問題だ。 /// メモ: メジャーメソッドやレイアウトメソッドの関係上、面倒みなきゃいけない気がする。 /// メモ: でも、ここで実装しなくとも、計測と配置が再計算されてるんじゃなかろうか。未確認。 //...(未実装)... // 進捗バーの全体の長さを設定するフィールドまたは関数の値、または // 進捗率の値を設定するフィールドまたは関数の値のどちらかが変更された時 if (barLengthFieldOrFunctionChanged || progressValueFieldOrFunctionChanged) { // フラグを下ろす barLengthFieldOrFunctionChanged = false; progressValueFieldOrFunctionChanged = false; // それを生成/破棄する必要があるかどうかを吟味 if (((barLengthField || (barLengthFunction != null)) && !barback) || ((progressValueField || (progressValueFunction != null)) && !bar)) { createProgressBar(); if (_showProgressValueText) createProgressValueTextDisplay(); } else if ( (!(barLengthField || (barLengthFunction != null)) && barback) || (!(progressValueField || (progressValueFunction != null)) && bar)) { destroyProgressBar(); if (_showProgressValueText) destroyProgressValueTextDisplay(); } } invalidateSize(); invalidateProperties(); // 進捗バーの全体の長さが変更された時 if (barLengthChanged) { // フラグを下ろす barLengthChanged = false; // 進捗バーの全体の長さを取得して保持 if (barLengthFunction != null) { barLength = barLengthFunction(data) as Number; } else { if (barLengthField) { try { if (barLengthField in data && data[barLengthField] != null) { barLength = data[barLengthField] as Number; } else { barLength = _defaultBarLength; } } catch (err : Error) { barLength = _defaultBarLength; } } else if (!barLengthField) { barLength = _defaultBarLength; } } // 進捗バーの全体の長さで進捗バーの背面描写 if (!barback) createProgressBar(); drawProgressbarBackground(); } invalidateSize(); invalidateProperties(); // 進捗率が変更された時 if (progressValueChanged) { // フラグを下ろす progressValueChanged = false; // 進捗率を取得して保持 if (progressValueFunction != null) { progressValue = progressValueFunction(data) as Number; } else if (progressValueField) { try { if (progressValueField in data && data[progressValueField] != null) { progressValue = data[progressValueField] as Number; } else { progressValue = 0; } } catch (err : Error) { progressValue = 0; } } // 進捗率をテキスト表記コンポーネントに設定して表示する if (showProgressValueText) { if (!progressValueTextDisplay) createProgressValueTextDisplay(); progressValueTextDisplay.text = progressValue.toString() + "\%"; } // 進捗率で進捗バーの前面描写 if (!bar) createProgressBar(); drawProgressbarFourground(); } invalidateSize(); invalidateProperties(); } //__________________________________________________________ // コンポーネントのデフォルトサイズと最小サイズ // を計算するメソッド /** * @private * * アイテムレンダラー自身によるサイズ設定方法を変更するには, * このメソッドをオーバーライドします. * * パフォーマンスに影響するため, 必要がない場合は * super.measure() を呼び出さないでください. * * (Auto Generated method stub) */ override protected function measure() : void { // 以下、独自に measure() メソッドを実装するので、 // super.measure() は呼び出さない. //super.measure(); // Disabled super.measure() // measure all the subcomponents here and set measuredWidth, measuredHeight, // measuredMinWidth, and measuredMinHeight // 計測値の初期化 var _measuredWidth : Number = 0; var _measuredHeight : Number = 0; var _measuredMinWidth : Number = 0; var _measuredMinHeight : Number = 0; // 水平レイアウトでいくつのセクションがあるかカウント var numHorizontalSections : uint = 0; if (iconDisplay) numHorizontalSections++; if (decoratorDisplay) numHorizontalSections++; if (labelDisplay || messageDisplay || progressValueTextDisplay) numHorizontalSections++; // 水平方向の padding 値と horizontalGap 値を取得 var paddingAndGapWidth : Number = getStyle("paddingLeft") + getStyle("paddingRight"); if (numHorizontalSections > 0) paddingAndGapWidth += (getStyle("horizontalGap") * (numHorizontalSections - 1)); // ラベル, メッセージ, 進捗率テキスト が設定されているかどうかを取得 var hasLabel : Boolean = labelDisplay && labelDisplay.text != ""; var hasMessage : Boolean = messageDisplay && messageDisplay.text != ""; var hasProgressValue : Boolean = _showProgressValueText && progressValueTextDisplay && progressValueTextDisplay.text != ""; // 垂直レイアウトでいくつのセクションがあるかカウント var numVerticalSections : uint = 0; if (hasLabel) numVerticalSections++; if (hasMessage) numVerticalSections++; if (hasProgressValue) numVerticalSections++; // 垂直方向の padding 値と verticalGap 値と複数個の verticalGap の総和を取得 var paddingHeight : Number = getStyle("paddingTop") + getStyle("paddingBottom"); var verticalGap : Number = (hasLabel || hasMessage) ? getStyle("verticalGap") : 0; var verticalGapSum : Number = verticalGap; if (numVerticalSections > 0) verticalGapSum += verticalGap * (numVerticalSections - 1); // アイコンの大きさを取得して、計測値に反映 var _iconWidth : Number = 0; var _iconHeight : Number = 0; if (iconDisplay) { _iconWidth = isNaN(iconWidth) ? getElementPreferredWidth(iconDisplay) : iconWidth; _iconHeight = isNaN(iconHeight) ? getElementPreferredHeight(iconDisplay) : iconHeight; _measuredWidth += _iconWidth; _measuredMinWidth += _iconWidth; _measuredHeight = Math.max(_measuredHeight, _iconHeight); _measuredMinHeight = Math.max(_measuredMinHeight, _iconHeight); } // デコレーターの大きさを取得して、計測値に反映 var _decoratorWidth : Number = 0; var _decoratorHeight : Number = 0; if (decoratorDisplay) { _decoratorWidth = getElementPreferredWidth(decoratorDisplay); _decoratorHeight = getElementPreferredHeight(decoratorDisplay); _measuredWidth += _decoratorWidth; _measuredMinWidth += _decoratorWidth; _measuredHeight = Math.max(_measuredHeight, _decoratorHeight); _measuredMinHeight = Math.max(_measuredMinHeight, _decoratorHeight); } // ラベルの大きさを取得 var _labelWidth : Number = 0; var _labelHeight : Number = 0; if (hasLabel) { if (labelDisplay.isTruncated) labelDisplay.text = labelText; _labelWidth = getElementPreferredWidth(labelDisplay); _labelHeight = getElementPreferredHeight(labelDisplay); } // メッセージの大きさを取得 var _messageWidth : Number = 0; var _messageHeight : Number = 0; if (hasMessage) { var _messageDisplayEstimatedWidth : Number = oldUnscaledWidth - paddingAndGapWidth - _iconWidth - _decoratorWidth; setElementSize(messageDisplay, _messageDisplayEstimatedWidth, NaN); _messageWidth = getElementPreferredWidth(messageDisplay); _messageHeight = getElementPreferredHeight(messageDisplay); } // 進捗率テキストと進捗バーの大きさを取得 var _progressWidth : Number = 0; var _progressHeight : Number = 0; _progressWidth = Math.max(data[_barLengthField] as Number, 0, getElementPreferredWidth(barback), getElementPreferredWidth(bar)); _progressHeight = Math.max(PGBAR_HEIGHT, 0, getElementPreferredHeight(barback), getElementPreferredHeight(bar)); if (hasProgressValue) { if (progressValueTextDisplay.isTruncated) progressValueTextDisplay.text = progressValue.toString() + "\%"; _progressWidth = Math.max(_progressWidth, getElementPreferredWidth(progressValueTextDisplay)); _progressHeight += getElementPreferredHeight(progressValueTextDisplay) + verticalGap; } // 以上の垂直レイアウト内の3つのコンポーネントの大きさを、計測値に反映 _measuredWidth += Math.max(_labelWidth, _messageWidth, _progressWidth); _measuredHeight = Math.max(_measuredHeight, _labelHeight + _progressHeight + _messageHeight + verticalGapSum); // padding 値や gap値を、計測値に反映 _measuredWidth += paddingAndGapWidth; _measuredMinWidth += paddingAndGapWidth; _measuredHeight += paddingHeight; _measuredMinHeight += paddingHeight; // 計測結果をコンポーネントの大きさに反映 measuredWidth = _measuredWidth; measuredHeight = _measuredHeight; measuredMinWidth = _measuredMinWidth; measuredMinHeight = _measuredMinHeight; } //__________________________________________________________ // 画面更新時に表示リストを更新できるように、 // コンポーネントをマークするメソッド /** * @private * * 次の画面更新時に updateDisplayList() メソッド * が呼び出されるように, コンポーネントをマークします. */ override public function invalidateDisplayList():void { // 再描写要求 redrawRequested = true; // スーパークラスの invalidateDisplayList() を呼び出す super.invalidateDisplayList(); } //__________________________________________________________ // アイテムレンダラーの背景の描写を行うメソッド /** * @private * * アイテムレンダラーの背景の描画方法を変更するには, * このメソッドをオーバーライドします. * * パフォーマンスに影響するため, 必要がない場合は * super.drawBackground() を呼び出さないでください. * * (Auto Generated method stub) */ override protected function drawBackground(unscaledWidth : Number, unscaledHeight : Number) : void { // スーパークラスの drawBackground() を呼び出して、背景を描写 super.drawBackground(unscaledWidth, unscaledHeight); // do any drawing for the background of the item renderer here } //__________________________________________________________ // アイテムレンダラーの背景の描写を行うメソッド /** * @private * * このアイテムレンダラーの背景の子の位置決めを変更するには、このメソッドを * オーバーライドします。パフォーマンスに影響するため、必要がない場合は * super.layoutContents() を呼び出さないでください。 * * (Auto Generated method stub) */ override protected function layoutContents(unscaledWidth : Number, unscaledHeight : Number) : void { // 以下、独自に layoutContents() メソッドを実装するので、 // super.layoutContents() は呼び出さない. //super.layoutContents(unscaledWidth, unscaledHeight); // Disabled super.layoutContents() // layout all the subcomponents here // TODO レイアウト再配置 // アイコンとデコレーターの大きさ var iconWidth : Number = 0; var iconHeight : Number = 0; var decoratorWidth : Number = 0; var decoratorHeight : Number = 0; // ラベル, メッセージ, 進捗率テキスト が設定されているかどうかを取得 var hasLabel : Boolean = labelDisplay && labelDisplay.text != ""; var hasMessage : Boolean = messageDisplay && messageDisplay.text != ""; var hasProgressValue : Boolean = _showProgressValueText && progressValueTextDisplay && progressValueTextDisplay.text != ""; // padding 値を取得 var paddingTop : Number = getStyle("paddingTop"); var paddingRight : Number = getStyle("paddingRight"); var paddingBottom : Number = getStyle("paddingBottom"); var paddingLeft : Number = getStyle("paddingLeft"); // Gap 値を取得 var horizontalGap : Number = getStyle("horizontalGap"); var verticalGap : Number = (hasLabel || hasMessage) ? getStyle("verticalGap") : 0; // 垂直方向の整列の取得 var verticalAlign : Number = -1; if (getStyle("verticalAlign") == "top") { verticalAlign = 0; } else if (getStyle("verticalAlign") == "bottom") { verticalAlign = 1; } else { // if (getStyle("verticalAlign") == "middle") verticalAlign = 0.5; } // 配置可能ビューの域 var viewWidth : Number = unscaledWidth - paddingLeft - paddingRight; var viewHeight : Number = unscaledHeight - paddingTop - paddingBottom; // アイコンの配置 if (iconDisplay) { // 大きさを設定する setElementSize(iconDisplay, this.iconWidth, this.iconHeight); // 大きさを取得する iconWidth = iconDisplay.getLayoutBoundsWidth(); iconHeight = iconDisplay.getLayoutBoundsHeight(); // 垂直方向の整列の計算 var iconDisplayY : Number = Math.round(verticalAlign * (viewHeight - iconHeight)) + paddingTop; // アイコンの配置 setElementPosition(iconDisplay, paddingLeft, iconDisplayY); } // デコレーターの配置 if (decoratorDisplay) { // 大きさを取得する decoratorWidth = getElementPreferredWidth(decoratorDisplay); decoratorHeight = getElementPreferredHeight(decoratorDisplay); // 大きさを設定する setElementSize(decoratorDisplay, decoratorWidth, decoratorHeight); // 垂直方向の整列 (いつでも垂直中央に表示する) の計算 var decoratorY : Number = Math.round(0.5 * (viewHeight - decoratorHeight)) + paddingTop; // デコレーターの配置 setElementPosition(decoratorDisplay, unscaledWidth - paddingRight - decoratorWidth, decoratorY); } // ラベル, メッセージ, 進捗バー の3つのコンポーネントを配置可能ビューの域とX座標 var labelComponentsViewWidth : Number = viewWidth - iconWidth - decoratorWidth; if (iconDisplay) labelComponentsViewWidth -= horizontalGap; if (decoratorDisplay) labelComponentsViewWidth -= horizontalGap; var labelComponentsX : Number = paddingLeft; if (iconDisplay) labelComponentsX += iconWidth + horizontalGap; // ラベルの高さ var labelTextHeight : Number = 0; if (hasLabel) { // ラベルが切り詰められていたら、テキストを再代入する if (labelDisplay.isTruncated) labelDisplay.text = labelText; // ラベルにスタイルをコミット labelDisplay.commitStyles(); // ラベルの高さを取得 labelTextHeight = getElementPreferredHeight(labelDisplay); } // メッセージにスタイルをコミット messageDisplay.commitStyles(); // 進捗率テキストの高さ var progressValueTextHeight : Number = 0; if (hasProgressValue) { // 進捗率テキストが切り詰められていたら、テキストを再代入する if (progressValueTextDisplay.isTruncated) progressValueTextDisplay.text = progressValue.toString() + "\%"; // 進捗率テキストにスタイルをコミット progressValueTextDisplay.commitStyles(); // 進捗率テキストの高さを取得 progressValueTextHeight = getElementPreferredHeight(progressValueTextDisplay); } // ラベル, メッセージ, 進捗コンポネ の3つのコンポーネントの大きさ var labelWidth : Number = 0; var labelHeight : Number = 0; var messageWidth : Number = 0; var messageHeight : Number = 0; var progressBarWidth : Number = 0; var progressBarHeight : Number = 0; var progressValueWidth : Number = 0; var progressValueHeight : Number = 0; var progressComponentsWidth : Number = 0; var progressComponentsHeight : Number = 0; // ラベルのサイズの調整 if (hasLabel) { labelWidth = Math.max(labelComponentsViewWidth, 0); labelHeight = labelTextHeight; if (labelWidth == 0) setElementSize(labelDisplay, NaN, 0); else setElementSize(labelDisplay, labelWidth, labelHeight); labelDisplay.truncateToFit("…"); } // 進捗コンポネサイズの調整 // 進捗バー if (bar || barback) { progressBarWidth = Math.max(getElementPreferredWidth(barback), barLength, defaultBarLength, 0); if (progressBarWidth == 0) { barLengthEstimated = 0; drawProgressbarBackground(); drawProgressbarFourground(); } else { if (progressBarWidth > labelComponentsViewWidth) { progressBarWidth = labelComponentsViewWidth; barLengthEstimated = labelComponentsViewWidth; drawProgressbarBackground(); drawProgressbarFourground(); } } progressBarHeight = Math.max(getElementPreferredHeight(barback), PGBAR_HEIGHT, 0); } else { barLengthEstimated = 0; createProgressBar(); progressBarWidth = Math.max(getElementPreferredWidth(barback), 0); progressBarHeight = Math.max(getElementPreferredHeight(barback), 0); } // 進捗率 if (hasProgressValue) { progressValueWidth = Math.max(getElementPreferredWidth(barback), 0); progressValueHeight = progressValueTextHeight; if (progressValueWidth == 0) { setElementSize(progressValueTextDisplay, NaN, 0); } else { setElementSize(progressValueTextDisplay, progressValueWidth, progressValueHeight); } progressValueTextDisplay.truncateToFit("…"); } // 進捗コンポネ progressComponentsWidth = Math.max(progressBarWidth, progressValueWidth, 0); progressComponentsHeight = progressBarHeight; if (_showProgressValueText) progressComponentsHeight += progressValueHeight + verticalGap; // メッセージのサイズの調整 if (hasMessage) { messageWidth = Math.max(labelComponentsViewWidth, 0); if (messageWidth == 0) { setElementSize(messageDisplay, NaN, 0); } else { var oldPreferredMessageHeight : Number = getElementPreferredHeight(messageDisplay); oldUnscaledWidth = unscaledWidth; setElementSize(messageDisplay, messageWidth, oldPreferredMessageHeight); var newPreferredMessageHeight : Number = getElementPreferredHeight(messageDisplay); if (oldPreferredMessageHeight != newPreferredMessageHeight) invalidateSize(); messageHeight = newPreferredMessageHeight; } } else { if (messageDisplay) setElementSize(messageDisplay, 0, 0); } // 垂直方向の整列用 var totalHeight : Number = 0; var labelComponentsY : Number = 0; var messageComponentsY : Number = 0; var progressComponentsY : Number = 0; var labelAlignmentHeight : Number = 0; var messageAlignmentHeight : Number = 0; var progressAlignmentHeight : Number = 0; // 垂直方向の整列のために、コンポーネントの高さを取得 if (hasLabel) labelAlignmentHeight = getElementPreferredHeight(labelDisplay); if (hasMessage) messageAlignmentHeight = getElementPreferredHeight(messageDisplay); progressAlignmentHeight = progressComponentsHeight; // 垂直配置するコンポーネント全部の高さを計算 totalHeight = labelAlignmentHeight + messageAlignmentHeight + progressAlignmentHeight; totalHeight += verticalGap * 2; // 各コンポーネントを配置するY座標を計算 labelComponentsY = Math.round(verticalAlign * (viewHeight - totalHeight)) + paddingTop; progressComponentsY = labelComponentsY + labelAlignmentHeight + verticalGap; messageComponentsY = progressComponentsY + progressAlignmentHeight + verticalGap; // ラベルの配置 if (labelDisplay) setElementPosition(labelDisplay, labelComponentsX, labelComponentsY); // 進捗コンポーネントの配置 setElementPosition(barback, labelComponentsX, progressComponentsY); setElementPosition(bar, labelComponentsX, progressComponentsY + 1); if (_showProgressValueText && progressValueTextDisplay) setElementPosition(progressValueTextDisplay, labelComponentsX, progressComponentsY + progressBarHeight + verticalGap); // メッセージの配置 if (messageDisplay) setElementPosition(messageDisplay, labelComponentsX, messageComponentsY); } } }
(Project)/src/assets/styles/defaults.css
/*############################################################## モバイル用独自 Flex コンポーネントのデフォルトスタイルシート ##############################################################*/ @namespace s "library://ns.adobe.com/flex/spark"; @namespace renderers "renderers.*"; /*============================================================== Application DPI = 240 ==============================================================*/ /*______________________________________________________________ 進捗バー付きアイコンアイテムレンダラー */ renderers|ProgressBarIconItemRenderer { paddingBottom: 12; paddingTop: 12; horizontalGap: 10; verticalGap: 9; iconDelay: 500; messageStyleName: "iconItemRendererMessageStyle"; progressBarBackgroundColor: #FFFFFF; progressBarDoneColor: #999900; progressBarBorderColor: #999999; progressValueTextStyleName: "progressBarIconItemRendererProgressValueTextStyle"; } /*______________________________________________________________ 進捗率コンポーネントのスタイル */ .progressBarIconItemRendererProgressValueTextStyle { fontSize: 16; color: #666666; textAlign: left; } /*============================================================== Application DPI = 160 ==============================================================*/ @media (application-dpi: 160) { renderers|ProgressBarIconItemRenderer { paddingBottom: 8; paddingTop: 8; horizontalGap: 8; verticalGap: 6; } .progressBarIconItemRendererProgressValueTextStyle { fontSize: 11; } } /*============================================================== Application DPI = 320 ==============================================================*/ @media (application-dpi: 320) { renderers|ProgressBarIconItemRenderer { paddingBottom: 16; paddingLeft: 16; horizontalGap: 16; verticalGap: 12; } .progressBarIconItemRendererProgressValueTextStyle { fontSize: 20; } }
ソースコードの解説の前に、確認
MXMLアイテムレンダラーを用いれば、配置や整列、大きさなどをほぼ自動で計算してくれて、マークアップ記述なのでラクラク書くことができるし、サクサクと任意のレンダラーを実装することができますが、処理が重くなってしまいます (参考:前回のエントリー)。
つまり、配置や整列や大きさの再計算に s:Group
を使うと、たしかに記述はとても楽ですが、処理がかなりモサモサになってしまうので、デスクトップ用Flexアプリ開発のときのように s:ItemRenderer
クラスを拡張したMXMLアイテムレンダラーによる実装はタブーとなります。
そもそも、Flex/AIRモバイルプロジェクトを Flash Builder 4.5/4.6 で作成したときは、アイテムレンダラークラスの新規作成時に、s:ItemRenderer
の継承が、新規作成ウィザードで出来なくなっていて、 s:LabelItemRenderer
クラス (モバイルプロジェクトでの s:List
コンポーネントにデフォルトで適用されている、モバイル用に最適化されたアイテムレンダラークラス) を継承したActionScriptアイテムレンダラーか、 s:IconItemRenderer
クラスを継承したMXMLアイテムレンダラーを、新規作成することになります。
ここで、 s:IconItemRenderer
を継承したMXMLアイテムレンダラーで、MXML記述によって独自のアイテムレンダラーを実装しようとしても、この s:IconItemRenderer
クラス も そのスーパークラスである s:LabelItemRenderer
クラスも、 spark.components.Group
クラスを継承していないために、 s:layout
による s:HorizontalLayout
や s:VerticalLayout
や s:TileLayout
に対応していないため、MXML(Flex) によるレイアウト処理(配置や整列、大きさなどの自動調整)機能が全く働かず、すべて自前で実装することになってしまい、しかもMXMLアイテムレンダラーだとActionScriptアイテムレンダラーよりも重くなってしまうので、MXMLアイテムレンダラーにする利点が全く考えられません。
そのため、凝ったモバイル用独自アイテムレンダラーを実装する際は、結局のところActionScript3.0でガツガツ記述することになります。その際、様々な知識が必要となるので、先ほどのソースコードを解説しながら、以下に、ポイントとしてまとめてみました。
Point 1. 継承するアイテムレンダラークラスに注意すること
Flex/AIRモバイルアプリプロジェクトでは、カスタムアイテムレンダラーを独自で実装する際に、デスクトップ版Flex/AIRアプリを開発するときのような要領で Group
クラスを継承してある ItemRenderer
クラスを継承するようなことはせずに、 UIComponent
クラスを継承してある LabelItemRenderer
クラスまたはそれをさらに継承してある IconItemRenderer
クラスを継承して作成することになります。
今回私は、 IconItemRenderer
クラスを継承して、実装しました (L.161
)。
Point 2. 独自にカスタマイズが必要なオーバーライドメソッドを把握すること
Flash Builder 4.5/4.6 で、モバイルプロジェクトにて、アイテムレンダラークラスを新規作成すると、テンプレートに基づいて次のように自動的にスタブ生成されます。下図は、 LabelItemRenderer
クラスを継承した myCustomItemRenderer
クラスを新規作成したときの状態です。
このとき、この自動スタブ生成されたオーバーライドメソッドを主にして、カスタマイズしていくことになります。
それぞれのオーバーライドメソッド/オーバーライドセッターでの処理内容を簡単に書くと、次の通りです。
- サブクラスで主にカスタマイズすることになるオーバーライドメソッド/セッター
- ・
data
セッター - データの変更に応答するときにこのプロパティを使用することになります。
基本的には、このsetterメソッドで受け取ったdata
をそのままsuper.data
に代入し、次回の画面更新時に値のコミット(反映)が行われるように、invalidateProperties()
メソッドを呼んでマーキングしてやるといいでしょう。 - ・
createChildren()
メソッド - アイテムレンダラーの子を作成・追加するときに使用することになります。
- ・
measure()
メソッド - アイテムレンダラー自身によるサイズ設定方法を変更するときに使用することになります。
新しいオプションパーツを自前で用意したときや子オブジェクトを追加したときなどに、このメソッドを自前で実装することになります。この時、パフォーマンスに影響するので、できるだけsuper.measure()
を呼び出さないようにします。
それ以外の時(つまり、アイテムレンダラー自身のサイズが変わるようなカスタマイズをしない時)には、super.measure()
を呼び出すのみにしておきます。 - ・
drawBackground()
メソッド - アイテムレンダラーの背景の描画方法を変更するときに使用することになります。
デフォルトの背景描画のままで良い場合は、super.drawBackground(unscaledWidth, unscaledHeight)
を呼び出すのみにしておきます。独自に背景の描画を行う場合は、できるだけsuper.drawBackground(unscaledWidth, unscaledHeight)
を呼び出さずに、このメソッドを自前で実装することになります。その際、あまり重い処理をしないようにし、複雑な描画処理をする時には、コンパイル済みFXGを使用するのが良いでしょう。 - ・
layoutContents()
メソッド - アイテムレンダラーの子要素のサイズや位置の設定方法を変更するときに使用することになります。
新しいオプションパーツを自前で用意したときや子オブジェクトを追加したときなどに、このメソッドを自前で実装することになります。この時、パフォーマンスに影響するので、できるだけsuper.layoutContents(unscaledWidth, unscaledHeight)
を呼び出さないようにします。
それ以外の時(つまり、アイテムレンダラーの子要素のサイズや位置などが変わるようなカスタマイズをしない時)には、super.layoutContents(unscaledWidth, unscaledHeight)
を呼び出すのみにしておきます。
- ・
- スタブ生成されないけど、オプションパーツを自前で追加したときに主にカスタマイズすることになるオーバーライドメソッド
- ・
styleChanged()
メソッド - スタイルプロパティの変更に応答するときに使用することになります。
super.styleChanged()
を呼び出したあと、独自に追加したスタイルプロパティの変更の応答を実装することになります。 - ・
commitProperties()
メソッド - コンポーネントに設定されたプロパティをコミット処理するときに使用することになります。
super.commitProperties()
を呼び出したあと、独自に追加したプロパティのコミット処理を実装することになります。
- ・
Point 3. テキストは、Labelコンポーネントではなく、StyleableTextFieldクラスを使用すること
PC版向けFlex/AIRアプリケーションの開発時には、独自カスタムアイテムレンダラークラスの実装時に、 spark.components.Label
クラスを用いて、テキスト表示をするのが、常套手段となりますが、モバイル用に最適化することを考慮すると、モバイル向けFlex/AIRアプリケーションに於ける独自カスタムアイテムレンダラークラスの実装時には、 spark.components.supportClasses.StyleableTextField
クラスを用いて、テキスト表示をすることになります。
カスタムアイテムレンダラークラスに自前で追加したテキスト表示 (StyleableTextField
インスタンス) に、CSSによるスタイルシートを適用・反映させるには、 mx.styles.CSSStyleDeclaration
クラスを介して、 自前で追加した StyleableTextField
インスタンスの styleDeclaration
プロパティに参照渡しをしてあげます。
この処理は、自前で追加した StyleableTextField
インスタンスの生成時と、このカスタムアイテムレンダラーに設定されているスタイルシートに変更を検出した時の 2 場面で必要となります。
Point 4. measure()メソッドのカスタマイズ実装に関して
モバイル向けに最適化するカスタムアイテムレンダラーに、新しいオプションパーツを自前で用意したときや子オブジェクトを追加したときなどには、 measure()
メソッドをオーバーライドして、カスタマイズする必要があります。
この measure()
メソッドでは、コンポーネントのデフォルトサイズと最小サイズを計測するという、非常に重要な処理を行います。
複雑な処理をするために、このメソッドの変更はパフォーマンスに影響します。独自にこのメソッド内をカスタマイズする際に必要がなければ、なるだけ super.measure()
を呼び出さないことが推奨されます。
自前で、 measure()
メソッドの処理を実装する際は、独自に追加したオプションパーツや子オブジェクトの個数、そして、それらの配置によって、 measuredWidth
及び measuredHeight
の算出を行います。したがって、どのような表示のカスタムアイテムレンダラーを実装するかによって、処理の複雑さは大きく変わってくるように思われます。
今回私が実装した ProgressBarIconItemRenderer
の場合、下図のような構成になるため、
・measuredWidth = paddingLeft + iconDisplay.width + ( horizontalGap * それが実際に挿入される個数 ) + ( labelDisplay.width と progressBar.width と progressValueTextDisplay.width と messageDisplay.width の中で最大のもの ) + decoratorDisplay.width + paddingRight
・measuredHeight = paddingTop + [ iconDisplay.height と { labelDisplay.height + ( verticalGap * それが実際に挿入される個数 ) + progressBar.height + progressValueTextDisplay.height + messageDisplay.height } と decoratorDisplay.height の中で最大のもの ] + paddingBottom
でそれぞれ計測することができます。
Point 5. layoutContents()メソッドのカスタマイズ実装に関して
...(後日追記)...
まとめ
...(後日追記)...
保守
このプログラムをライブラリとして、Mio Project et cetera - /root/dev/MioLabs/lib/PBIIR/src/net/chsmea/mioproject/lib/mobi/renderers/ProgressBarIconItemRenderer.as にて保守していくことにしました。したがって、このライブラリのソースコードの最新版は、そちらから入手できるようになります。
じつは、ver 1.0.0 のソースコードを公開してから、いくらか些細な不具合を見つけてしまった(#17, #18)ので、気が向いたときにコソコソと直していくかもしれません。この他にも不具合や疑問等がありましたら、皆さんからもドシドシとチケットを切っていただいても構いません。大歓迎です!
.
- 2011/12/10 02:48
- Permalink
- nmio
- Comment(0)
- TB(0)
comment