Sencha Touchのカスタムコンポーネントを作る、パート2
こんにちは、ゼノフィkotsutsumiです。

Sencha Touch 2.1のコンポーネント作りのチュートリアルのパート2です。 このチュートリアルのパート1ではSencha Touchのコンポーネントのコンセプト、 Ext.tux.AudioCover のアイディアを紹介し、Ext.tux.AudioCoverを成功させる為に必要な機能を定義し始めました。 今回はコンフィグのパラメーターから、その定義内容の説明を続けます。
コンフィグのパラメタ
全ての良いコンポーネントでは開発者がアプリケーションのニーズに応じた設定ができるように、コンフィグパラメーターのセットを提供すべきです。次にこのコンポーネントの主なコンフィグ定義について説明します。
/** * @class Ext.tux.AudioCover * @extend Ext.Audio * @author Andrea Cammarata */ Ext.define('Ext.tux.AudioCover',{ extend: 'Ext.Audio', xtype: 'audiocover', config: { /** * @cfg coverUrl * オーディオファイルのカバーとして使う * 画像のurl * @accessor */ coverUrl: null, /** * @cfg {Object} stopButton * ストップボタンの設定 */ stopButton: { iconMask: true, iconCls: 'stop', ui: 'white' } }, cachedConfig: { /** * @cfg baseCls * コンポーネントを描画する時のベースCSSクラス * @accessor */ baseCls: 'x-tux-audiocover', /** * @cfg enableControls * オーディオファイルをコントロールするカスタムUIを提供するので * デフォルトのブラウザーコントロールを非表示にする * @accessor */ enableControls: false }, ...
"config" と "cachedConfig" という、二つの違うタイプのコンフィグを定義しました。
最初のは開発者が違う値を設定したり、値を変更したりしてコンポーネントの見た目と動作をカスタマイズできるようにしています。
まず、パブリックな設定から見てみます。 コンポーネントにバインドしているオーディオトラックのカバーに使われるイメージを指定する、 “coverUrl”プロパティを定義しています。 二つ目のプロパティ、“stopButton”はオーディオのストップボタンを作るために使われます。 後者については、 このデフォルトの設定(ボタンのUIやアイコンなど)を定義してありますが、 好きなように変更できます。
「この2つのコンフィグしか公開されていないのなら、再生したいオーディオトラックのURLはどこで指定するの?」と思うでしょう。 忘れてはいけないのは、我々は今Ext.Audioのコンポーネントを拡張しているということです。 それによって、定義したコンフィグパラメーター以外に、親クラスのコンポーネントで定義されたものもあります。 従って再生するオーディオファイルのURLは、Ext.Audioスーパークラスの公開プロパティである“url”コンフィグに定義します。
“cachedConfig”のなかに定義されている“baseCls”プロパティについて、一つだけ必要な知識があります。 カスタムUIに使う全ての子エレメントのCSSデフォルトルートクラスには、”x-tux-audiocover”を使います。
最後に、”cachedConfig”の中の最後のプロパティ“enableControls”がfalseと設定されているのがわかりますね。 これは、ブラウザーはオーディオやビデオファイルをコントロールするためのメディアエレメントのデフォルトのユーザーインターフェースを自動的に描画してしまうので、必要なのです。 我々の目的はカスタムUIを定義することですから、デフォルトのレンダリングはしないようにブラウザーに頼まないといけません。
以上で全てのコンフィグが定義されたので、本当に必要な機能を書き始めることができます。
関数は8つだけ!
おいしすぎる話に聞こえるかもしれませんが、コンポーネントを動作させるためには8つの簡単な関数だけでよいのです。では、その関数を見てみましょう。
初期化
ほとんどのコンポーネントは使う前に初期化が必要です。
/** * Component initialization function. * @private */ initialize: function(){ var me = this; /* 経過時間とスライスの回転とを同期する * ことができるように * Ext.Audio関数にリスナーを設定する */ me.on({ timeupdate: 'onUpdateTime', ended: 'showFront', scope: me }); /* ユーザーがタップしたときに裏面を表示するために * カバーエレメントにハンドラーを設定する me.coverEl.on('tap', 'showBack', me); // Ext.Audio コンポーネントの初期化 me.callParent(arguments); }
これがコンポーネントの初期化部分です。 初期化中には、コンポーネントがExt.Audioコンポーネントが公開する2つのパブリックイベントのリスナーをセットしています。 “onUpdate”関数のブログレスモーションバーと同期して、オーディオファイルが終わったら“showFront”関数を呼んでTUXをアイドル状態にします。
カバーエレメントにバインドされている”tap”ハンドラーに気づくことも重要です。 この関数はこのハンドラーとバインドされていて、ユーザーがコンポーネントをタッチした時に“showBack”がプログレスバーのある裏面を表示します。
フリップのアニメーション
コンポーネントの初期化が完成したので、今度はフリップアニメーションを使ってTUXの表や裏を表示する3つの簡単な関数について見ていきましょう。
フリップアクションのデモ。
/** * カバーフェースを表示しオーディオを停止します */ showFront: function(){ this.flip(true); }, /** * プログレスバーを表示しオーディオを開始します */ showBack: function(){ this.flip(false); }, /** * カードを表/裏にフリップして * オーディオファイルを再生/停止します * @param {Boolean} toFront trueにすると表のカバーを表示、 * それ以外は裏のプログレスバーを表示。 * @private */ flip: function(toFront){ // Play or stop tha audio file this[toFront ? 'stop' : 'play'](); // Flip the card element this.cardEl.setStyle('-webkit-transform', Ext.String.format('rotateY({0}deg)', toFront ? 0 : 180)); }
“showFront”と“showBack”という2つの簡単な関数を定義しました。 やっているのは3つ目の関数に“frip”しろと伝えることだけです。 もし表面を表示したければBoolean型のパラメーターをtrueに、裏面を表示したければfalseに設定します。
もっと詳しく説明するならば、オーディオファイルを再生または停止し、コンテナーの”cardEl”エレメントを表面を表示する時には0°に、裏面を表示する時には180°に回転させます。 コンポーネントがこのフリップトランジションをするには1秒必要です。 このスピードはSass定義中の次の簡単なCSSルールで設定しています。
-webkit-transition: -webkit-transform 1s;
カバーイメージのアップデート
カバーイメージのアップデートはもっと簡単です。 実は、もうSencha Touchについて基本的な知識があったらご存じでしょう。 “ClassManager”は”config”のオブジェクトの中のプロパティには、 get, apply, updateにキャピタライズしたプロパティ名を繋げた3つの関数を自動的に定義します。 この場合は次のものが自動的に定義される関数です:
- getCoverUrl: オーディオファイルと関連づけられているカバーイメージのURLを取り出す
- applyCoverUrl: カバーイメージの参照元をカバー指定したURLの値と置き換える
- updateCoverUrl: 古い設定を新しいので更新する為に”applyCoverUrl”をコールした直後にコールされる
前述のように、この関数は自動的に生成されますが、必要であればオーバーライドできます。
coverUrlが設定されたら、設定値と新しく指定されたURLを更新したいのですが、 それは次の様に“updateCoverUrl”をオーバーライドするだけですみます:
/** * オーディオカバーUrlを更新する * @param {String} 新しいオーディオカバーurlの値 * @private */ updateCoverUrl: function(value){ this.coverEl.setStyle('background', Ext.String.format('url({0})', value)); }
こうすると、コンポーネントがページにレンダリングされた直後や、カバーイメージをアップデートをしたい時には”coverUrl”の背景としてセットされます!
ストップボタン

ユーザーがオーディオトラックを停止できるようにするために、裏面の丸いプログレスバーの真ん中にそれ専用のボタンを作らなければなりません:
/** * ストップボタンを生成しgetStopButtonの参照を更新する * @param {Object} config ボタンを生成するのに使うコンフィグ * @private */ applyStopButton: function(config) { return Ext.factory(config, Ext.Button, this.getStopButton()); }, /** * 古いボタンが削除され新しいボタンが生成されるときに * ストップボタンを更新する * @param {Ext.Button} newButton 生成された新しいボタン * @param {Ext.Button} oldButton 直近の既存の古いボタン * @private */ updateStopButton: function(newButton, oldButton){ if (newButton) { newButton.renderTo(this.progressEl); newButton.on('tap', 'showFront', this); } else if (oldButton) { oldButton.destroy(); } }
“applyStopButton”はコンポーネントがレンダリングされる時に自動的に呼びだされます。 これは、“stopButton”コンフィグの中での指定した、 デフォルトコンフィグオブジェクトが唯一のパラメタとして渡されます。
この場合は、デフォルトの設定を“Ext.Factory”関数に渡して、“Ext.Button”コンポーネントを生成して返しています。 この関数は本当に素晴らしくて、これで参照関数(この場合は“getStopButton”)を更新できます。 こうすると、例えばボタンのUIコンフィグを”white”から”action”に変更したいような場合でも、 次のコードで新しく生成されたストップボタンを取得するして設定することができます。
<CMP>.getStopButton().setUi('action');
"<CMP>"は、更新したい”Ext.tux.Audiocover”コンポーネントです。
プログレスバーを動かそう

コンポーネントの中で最後に定義しなければならないのは、オーディオファイルの再生時間とスライスの回転とを常にシンクロさせる“onUpdateTime”関数です:
/** * プログレス効果をシミュレートするスライスの回転に * 同期させるコアコンポーネント関数。 * この関数はメディアエレメントの現在の時間が変更される * 度に呼び出される * @param {Ext.Media} media リンクするメディアエレメント * @param {Number} time 現在のメディアの時間(秒単位) * @private. */ onUpdateTime: function(media, time){ var deg1 = 0, deg2 = 180; if(time === 0) { return; } // 新しいスライスの回転値を計算 var sliceDeg = (time * 360) / this.getDuration(); if(sliceDeg === 0) { return; } /* 現在時間が半分を過ぎたら最初のスライスの * 代わりに2つめのスライスを回転させる */ if(sliceDeg > 180) { deg1 = 180; deg2 = sliceDeg; } else { deg1 = sliceDeg; } // 進行状況をシミュレートするためにスライスを回転させる this.slice1.setStyle('-webkit-transform', Ext.String.format('rotateZ({0}deg)', deg1)); this.slice2.setStyle('-webkit-transform', Ext.String.format('rotateZ({0}deg)', deg2)); }
この場合でも、コードのロジックはとても簡単です。 計算は簡単な数学的な比率で行われます。 “time”は現在のトラックのプレータイム(1秒、30秒..など)、 360°が回転の最大値で”this.getDuration()”でこのオーディオトラックの総時間がわかります。
この短いコードで、2つのうちの1つのスライスをどれぐらい回転させればよいかが分かりますが、どちらを回転させるかはまだわかりません。 丸いプログレスバーが360°全てをカバーするためには、各スライスが最大で180°回転できる2つのスライスを使う必要があります(これから定義するCSSクリップのルールのため)。 この理由により、上の計算で取得した回転の値が180°より少なければ、最初のスライスを回転させ、そうでなければ2つ目のスライスを回転させる必要があります。
これから定義するCSSクリップのルールによって、もし最初のスライスの回転が0と等しければ完全に非表示になり、180°だったら完全に表示される状態になります。 回転値が180°以上の場合は、 最初のスライスは180°にキープされる必要があり、 2つ目のスライスの回転値が更新される必要があります。 最初のスライスの回転値を180°にキープしないと、2つめのスライスを更新するときに、丸いプログレスバーの右半分が白くて左半分が青くなっていくという変な動きになってしまいます。 この方法で、TUXは360°全てをカバーした丸いプログレスをスムースに表示することができます。
コードについて最後の考察
さあ、やっと誇りをもってコンポーネントのソースコードが完成したと言えます。 Sencha Touchはとてもフレキシブルなフレームワークで簡単にカスタマイズできますから、 次に新しいWebアプリを作り始める時には、単にデフォルトのコンポーネントだけを使うことに縛られないようにしましょう。 見た目がユニークなアプリケーションを目指して、何かオリジナルの物を構築することで、クリエイティブなところを伸ばしましょう。
我々のコンポーネントを再び見てみると、もうすこしCSSの定義に手を入れる必要があります。 このチュートリアルの最後のパートでは、 Ext.tux.AudioCoverが、単に機能性のあるコンポーネントにとどまらない、 洗練された外観で製品にすぐに使えるものするために、 コンポーネントに使うスタイリングのレビューをして、完成させます。