Android には、UI 作成のための高度で強力なコンポーネント化モデルが用意されています。ベースになるのは基本的なレイアウト クラス View と ViewGroup です。プラットフォームには、ビルド済みのさまざまな View サブクラスと ViewGroup サブクラス(ウィジェットおよびレイアウトと呼ばれます)が組み込まれており、UI の作成に利用できます。
利用可能なウィジェットとしては、Button、TextView、EditText、ListView、CheckBox、RadioButton、Gallery、Spinner などがあり、特別な用途を持つ AutoCompleteTextView、ImageSwitcher、TextSwitcher などもあります。
利用可能なレイアウトとしては、LinearLayout、FrameLayout、RelativeLayout などがあります。その他の例については、一般的なレイアウトをご覧ください。
ビルド済みのウィジェットまたはレイアウトの中に自分の用途に適したものがない場合は、独自の View サブクラスを作成できます。既存のウィジェットやレイアウトを少し調整するだけで済む場合は、対象のウィジェットまたはレイアウトをサブクラス化して、そのメソッドをオーバーライドできます。
独自の View サブクラスを作成すると、画面要素の外観や機能をきめ細かく制御できます。カスタムビューを使用することで、以下のような機能を実現できます。
-
フルカスタム レンダリングの
Viewタイプを作成できます。たとえば、2D グラフィックを使用して、アナログ電子制御装置のような「音量調節」つまみを表示できます。 -
Viewコンポーネントのグループを新しい単一のコンポーネントに結合できます。たとえば、コンボボックス(ポップアップ リストと自由入力テキスト フィールドの組み合わせ)、デュアルペイン セレクタ コントロール(左右のペインにリストがあり、各リストの項目を再割り当てできる)などを作成できます。 -
EditTextコンポーネントが画面にレンダリングされる方法をオーバーライドできます。 NotePad サンプルアプリでは、この手法を活用して、罫線付きのメモ帳を作成しています。 - キーの押下などの他のイベントをキャプチャして、独自の方法で処理できます(ゲームなどに利用できます)。
以下のセクションでは、カスタムビューを作成してアプリ内で利用する方法について説明します。詳細については、View クラスをご覧ください。
基本的なアプローチ
独自の View コンポーネントを作成する際に必要となる知識の概要を次に示します。
-
既存の
Viewクラスまたはサブクラスを、独自のクラスを使用して拡張します。 -
スーパークラスの一部のメソッドをオーバーライドします。オーバーライドの対象となるスーパークラス メソッドは、先頭に
onが付いているものです(onDraw()、onMeasure()、onKeyDown()など)。これは、ライフサイクルなどの機能フックの際に、ActivityまたはListActivityのonイベントをオーバーライドするのと同様です。 - 新しい拡張クラスを使用します。完成した新しい拡張クラスは、ベースとなったビューの代わりとして使用できます。
フルカスタム コンポーネント
フルカスタム コンポーネントを使用すれば、あらゆる外観のグラフィカル コンポーネントを自由に作成できます。たとえば、アナログ目盛りのようなグラフィカルなボリューム メーターや、カラオケで曲に合わせて歌を歌えるようにボールがバウンドしながら歌詞の間を移動するテキストビューなどを作成できます。組込みコンポーネントを組み合わせるだけでは実現できないものが必要になる場合があります。
幸いなことに、コンポーネントは想像力、画面サイズ、利用可能な処理能力の範囲内で、望みどおりの外観と動作を実現できます。ただし、アプリケーションはデスクトップ ワークステーションよりも大幅に処理能力の低いデバイスで実行される可能性があることを念頭に置いてください。
フルカスタム コンポーネントを作成する際は、次の点を考慮してください。
-
拡張可能なビューの中で最も汎用性があるのは
Viewです。したがって、通常はこれを拡張して新しいスーパー コンポーネントを作成することから始めます。 - 属性とパラメータを XML から受け取ることができるコンストラクタを作成することも、独自の属性とパラメータを利用することもできます(たとえば、音量メーターの色と範囲、メーター針の幅と減衰量などを指定できます)。
- 通常は、独自のイベント リスナー、プロパティ アクセサ、修飾子を作成し、コンポーネント クラスに高度な動作を組み込みます。
-
ほとんどの場合は
onMeasure()をオーバーライドします。コンポーネントで何かを表示する場合は、おそらくonDraw()もオーバーライドする必要があります。どちらにもデフォルト動作が設定されており、デフォルトのonDraw()は何も実行せず、デフォルトのonMeasure()は常に 100×100 のサイズを設定します。このサイズがニーズを満たすことはあまりないと思われます。 -
必要に応じて、他の
onメソッドをオーバーライドすることもできます。
onDraw() と onMeasure() を拡張する
onDraw() メソッドは、Canvas を備えています。この上に、2D グラフィックや、各種の標準コンポーネントやカスタム コンポーネント、スタイル付きテキストなど、必要なものをすべて実装できます。
onMeasure() の場合はもう少し複雑です。onMeasure() は、コンポーネントとそのコンテナとの間のレンダリング コントラクトにとって欠かせない要素です。内部パーツの測定値を効率的かつ正確にレポートするには、onMeasure() をオーバーライドする必要があります。また、親からの制限に関する要件(onMeasure() メソッドに渡します)と、setMeasuredDimension() メソッドを呼び出す要件(計算された測定幅と測定高さを指定します)があるため、もう少し複雑になります。オーバーライドした onMeasure() メソッドからこのメソッドを呼び出さないと、測定時に例外が発生します。
onMeasure() の実装方法の概要を次に示します。
-
オーバーライドした
onMeasure()メソッドを呼び出す際は、幅と高さの仕様を指定します。この仕様は、生成する幅と高さの測定値に対する制限の要件として扱われます。widthMeasureSpecパラメータとheightMeasureSpecパラメータはどちらもディメンションを表す整数コードです。この仕様で要求される制限の種類の詳細については、View.onMeasure(int, int)のリファレンス ドキュメントをご覧ください。このリファレンス ドキュメントには、測定処理全般の説明もあります。 -
コンポーネントの
onMeasure()メソッドは、コンポーネントのレンダリングに必要な幅と高さの測定値を計算します。渡された仕様内に留まるように試みる必要がありますが、仕様を超えることもあります。この場合、親はクリッピング、スクロール、例外のスロー、onMeasure()に再試行を求める(おそらく異なる測定仕様で)など、何を行うかを選択できます。 -
幅と高さが計算されたら、計算された測定値を指定して
setMeasuredDimension(int width, int height)メソッドを呼び出します。そうしないと、例外が発生します。
フレームワークがビューで呼び出すその他の標準メソッドの概要を次に示します。
| カテゴリ | メソッド | 説明 |
|---|---|---|
| 作成 | コンストラクタ | ビューがコードから作成されるときに呼び出される形式のコンストラクタと、ビューがレイアウト ファイルからインフレートされるときに呼び出される形式のコンストラクタがあります。後者の形式では、レイアウト ファイル内で定義されている属性が解析されて適用されます。 |
|
このビューとそのすべての子が XML からインフレートされたときに呼び出されます。 | |
| レイアウト | |
このビューとそのすべての子のサイズ要件を指定するために呼び出されます。 |
|
このビューが自身の子すべてにサイズと位置を割り当てる必要があるときに呼び出されます。 | |
|
このビューのサイズが変更されたときに呼び出されます。 | |
| 描画 | |
ビューがコンテンツをレンダリングする必要があるときに呼び出されます。 |
| イベント処理 | |
キーダウン イベントが発生したときに呼び出されます。 |
|
キーの UP イベントが発生したときに呼び出されます。 | |
|
トラックボールのモーション イベントが発生したときに呼び出されます。 | |
|
タッチスクリーンのモーション イベントが発生したときに呼び出されます。 | |
| フォーカス | |
ビューがフォーカスを取得したときまたは喪失したときに呼び出されます。 |
|
ビューを格納するウィンドウがフォーカスを取得したときまたは喪失したときに呼び出されます。 | |
| アタッチ | |
ビューがウィンドウにアタッチされたときに呼び出されます。 |
|
ビューがウィンドウからデタッチされたときに呼び出されます。 | |
|
ビューを格納するウィンドウの表示設定が変化したときに呼び出されます。 |
複合コントロール
フルカスタム コンポーネントを作成するのではなく、既存のコントロールを集めて再利用可能なコンポーネントを作成したい場合は、複合コンポーネント(複合コントロール)の作成が最適です。複合コンポーネントとは、簡単に説明すると、比較的基本的なコントロールまたはビューをいくつか集めてアイテムの論理グループを構成し、単体として扱えるようにする仕組みです。たとえば、コンボボックスは、単一行の EditText フィールドと、その横のポップアップ リスト付きのボタンを組み合わせたものと考えることができます。ボタンをタップしてリストからアイテムを選択すると、選択したアイテムが EditText フィールドに入力されますが、ユーザーは必要に応じて EditText に直接入力することもできます。
Android では、Spinner と AutoCompleteTextView という 2 つの View を利用する方が簡単です。いずれにしても、このコンボボックスのコンセプトは良い例になります。
複合コンポーネントを作成する手順は次のとおりです。
-
Activityと同様に、宣言型の(XML ベースの)アプローチを使用して内部コンポーネントを作成することも、コードを使用してプログラムで内部コンポーネントをネストすることもできます。通常はなんらかのタイプのLayoutを出発点にして、Layoutを拡張するクラスを作成します。コンボボックスの場合、通常は水平方向のLinearLayoutを使用します。他のレイアウトを内部にネストすることもできます。つまり、複合コンポーネントの構造はいくらでも複雑にすることができます。 -
まず、新しいクラスのコンストラクタ内で、スーパークラスが必要とするパラメータをすべて受け取り、そのままスーパークラス コンストラクタに渡します。次に、新しいコンポーネントの内部で使用するその他のビューを設定します。ここで、
EditTextフィールドとポップアップ リストを作成します。コンストラクタで抽出して使用できる独自の属性とパラメータを XML に導入することもできます。 -
必要に応じて、内部ビューが生成するイベントのリスナーを作成します。たとえば、リストアイテム クリック リスナー用のリスナー メソッドを使用して、リストアイテムが選択されたときに
EditTextの内容を更新できます。 -
必要に応じて、アクセサと修飾子を使用して独自のプロパティを作成します。たとえば、コンポーネント内で最初に
EditText値を設定できるようにしておいて、必要なときにその内容をクエリできます。 -
必要に応じて、
onDraw()とonMeasure()をオーバーライドします。Layoutを拡張する場合、レイアウトにはデフォルトの動作があり、通常はその動作で十分であるため、この処理は通常は必要ありません。 -
必要に応じて、他の
onメソッド(onKeyDown()など)をオーバーライドします。たとえば、特定のキーがタップされたときにコンボボックスのポップアップ リストから特定のデフォルト値が選択されるようにします。
カスタム コントロールのベースとして Layout を使用するメリットは次のとおりです。
- アクティビティ画面と同様、宣言型の XML ファイルを使用してレイアウトを指定することも、コードを使用してプログラムでビューを作成し、レイアウト内にネストすることもできます。
-
onDraw()メソッドとonMeasure()メソッド、および他のほとんどのonメソッドには、適切な動作が設定されているため、オーバーライドする必要はありません。 - 複雑な複合ビューを簡単かつ自由に作成して、単体のコンポーネントであるかのように再利用できます。
既存のビュータイプを変更する
必要とする機能と似た機能を備えたコンポーネントがすでに存在している場合、そのコンポーネントを拡張し、変更したい動作をオーバーライドできます。フルカスタム コンポーネントの場合と同様の設定が可能な一方、View 階層の中で用途が特化しているクラスをベースに作業を開始することで、労力をかけることなく、必要な動作をそのまま流用することができます。
たとえば、NotePad サンプルアプリは、Android プラットフォームのさまざまな活用方法を紹介しています。その一例として、EditText ビューを拡張することで、罫線付きのメモ帳を作成しています。このサンプルは完全ではなく、罫線を付けるための API が変更される可能性がありますが、基本原則の理解には役立ちます。
まだ Android Studio に NotePad サンプルをインポートしていない場合は、インポートしてください(上記のリンクからソースを参照するだけでも構いません)。特に、NoteEditor.java ファイル内にある LinedEditText の定義をご覧ください。
このファイルの主なポイントは以下のとおりです。
-
定義
クラスは次の行で定義されています。
public static class LinedEditText extends EditTextLinedEditTextは、NoteEditorアクティビティの内部クラスとして定義されていますが、パブリック クラスであるため、NoteEditorクラスの外部からNoteEditor.LinedEditTextとしてアクセスできます。また、
LinedEditTextはstaticです。つまり、親クラスからのデータアクセスを許可する「合成メソッド」は生成しません。つまり、NoteEditorとの強い関連性はなく、独立したクラスとして動作します。外部クラスから状態にアクセスする必要がない場合は、この単純な方法で内部クラスを作成できます。生成されるクラスは小さいため、他のクラスから簡単に利用できます。LinedEditTextはEditTextを拡張します。この場合、EditTextはカスタマイズするビューです。完成した新しいクラスは、標準のEditTextビューの代わりとして利用できます。 -
クラスの初期化
通常どおり、先にスーパークラスを呼び出します。これはデフォルト コンストラクタではなく、パラメータ化コンストラクタです。
EditTextは、XML レイアウト ファイルからインフレートされるときに、このパラメータを使用して作成されます。そのため、このコンストラクタは、パラメータを受け取る処理と、受け取ったパラメータをスーパークラス コンストラクタに渡す処理の両方を行う必要があります。 -
オーバーライドされたメソッド
上記のサンプルでオーバーライドされているメソッドは
onDraw()だけですが、独自のカスタム コンポーネントを作成する場合は、必要に応じて他のメソッドもオーバーライドします。上記のサンプルでは、
onDraw()メソッドをオーバーライドすることで、EditTextビューのキャンバス上に青色の線を描画できるようにしています。キャンバスは、オーバーライドされたonDraw()メソッドに渡されます。メソッドが終了する前に、super.onDraw()メソッドが呼び出されます。スーパークラス メソッドを呼び出す必要があります。上記の例の場合、追加する線を描画した後、最後に呼び出しています。 -
カスタム コンポーネント
カスタム コンポーネントを作成した後は、その使用方法を指定する必要があります。NotePad サンプルの場合、レイアウトの宣言から直接、カスタム コンポーネントを使用しています。そこで、
res/layoutフォルダにあるnote_editor.xmlを見てみましょう。<view xmlns:android="http://schemas.android.com/apk/res/android" class="com.example.android.notepad.NoteEditor$LinedEditText" android:id="@+id/note" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/transparent" android:padding="5dp" android:scrollbars="vertical" android:fadingEdge="vertical" android:gravity="top" android:textSize="22sp" android:capitalize="sentences" />
カスタム コンポーネントは XML 内で汎用ビューとして作成されており、そのクラスはフルパッケージを使用して指定されています。定義した内部クラスは、
NoteEditor$LinedEditTextという表記を使用して参照されます。これは、Java プログラミング言語において、内部クラスを参照する際の標準的な方法です。カスタムビュー コンポーネントが内部クラスとして定義されていない場合は、XML 要素名を使用してビュー コンポーネントを宣言し、
class属性を除外できます。次に例を示します。<com.example.android.notepad.LinedEditText id="@+id/note" ... />
この場合、
LinedEditTextクラスは独立したクラスファイルになります。このクラスがNoteEditorクラス内にネストされている場合、この方法は機能しません。定義内の他の属性とパラメータは、カスタム コンポーネント コンストラクタに渡され、そのまま
EditTextコンストラクタに渡されます。つまり、EditTextビューで使用するパラメータと同じパラメータになります。独自のパラメータを追加することもできます。
カスタム コンポーネントの作成は、必要に応じて複雑にすることができます。
高度なコンポーネントの場合、さらに多くの on メソッドをオーバーライドし、そのヘルパー メソッドを導入することで、プロパティや動作を大幅にカスタマイズできます。想像力さえあれば、必要な機能に応じて、自由自在にコンポーネントをカスタマイズすることができます。