カスタムビュー コンポーネントを作成する

Compose を試す
Jetpack Compose は、Android に推奨される UI ツールキットです。Compose でレイアウトを操作する方法を学習します。

Android は、基本的なレイアウト クラス ViewViewGroup に基づいて、UI を作成するための高度で強力なコンポーネント化されたモデルを提供します。このプラットフォームには、事前ビルド済みのさまざまな View サブクラスと ViewGroup サブクラス(それぞれウィジェットとレイアウトと呼ばれます)が用意されており、UI の作成に使用できます。

利用可能なウィジェットとしては、ButtonTextViewEditTextListViewCheckBoxRadioButtonGallerySpinner などがあり、特別な用途を持つ AutoCompleteTextViewImageSwitcherTextSwitcher などもあります。

使用可能なレイアウトには、LinearLayoutFrameLayoutRelativeLayout などがあります。その他の例については、一般的なレイアウトをご覧ください。

ビルド済みのウィジェットやレイアウトがニーズに合わない場合は、独自の View サブクラスを作成できます。既存のウィジェットやレイアウトをわずかに調整するだけでよい場合は、ウィジェットまたはレイアウトをサブクラス化して、そのメソッドをオーバーライドできます。

独自の View サブクラスを作成すると、画面要素の外観と機能を細かく制御できます。カスタムビューを使用すると、どのようなことができるか、いくつか例を示します。

  • 完全にカスタム レンダリングされた View タイプを作成できます。たとえば、2D グラフィックを使用してレンダリングされ、アナログ電子制御装置に似た「音量コントロール」ノブを作成できます。
  • View コンポーネントのグループを 1 つのコンポーネントにまとめると、コンボボックス(ポップアップ リストと自由入力テキスト フィールドの組み合わせ)、デュアルペイン セレクタ コントロール(左右のペインにリストが表示され、どのアイテムをどのリストに含めるかを再割り当てできる)などを作成できます。
  • EditText コンポーネントの画面上のレンダリング方法をオーバーライドできます。サンプルアプリの NotePad では、この機能を使って、枠線付きのメモ帳のページを作成しています。
  • キーの押下などの他のイベントをキャプチャし、ゲームなどのカスタム方法で処理できます。

以降のセクションでは、カスタムビューを作成して、アプリケーションで使用する方法について説明します。詳細については、View クラスをご覧ください。

基本的なアプローチ

ここでは、独自の View コンポーネントを作成するために知っておくべきことの概要を示します。

  1. 既存の View クラスまたはサブクラスを独自のクラスで拡張します。
  2. スーパークラスの一部のメソッドをオーバーライドします。オーバーライドするスーパークラスのメソッドは on で始まります(例: onDraw()onMeasure()onKeyDown())。これは、ライフサイクルやその他の機能フックでオーバーライドする Activity または ListActivityon イベントに似ています。
  3. 新しい拡張クラスを使用します。完了したら、ベースとなったビューの代わりに新しい拡張機能クラスを使用できます。

完全にカスタマイズされたコンポーネント

必要に応じて完全にカスタマイズされたグラフィカル コンポーネントを作成できます。たとえば、古いアナログ ゲージのようなグラフィックの VU メーターや、カラオケマシンのそばで歌っているときに単語に合わせて弾むボールが動くテキストビューなどです。どのように組み合わせても、組み込みコンポーネントでは実現できない処理が必要になる場合があります。

幸いなことに、想像力、画面のサイズ、利用可能な処理能力によってのみ、制限された外観と動作のコンポーネントを作成できます。ただし、アプリケーションはデスクトップ ワークステーションよりも大幅に低い電力で動作しなければならない場合があることに留意してください。

完全にカスタマイズされたコンポーネントを作成するには、次の点を考慮してください。

  • 拡張できる最も汎用的なビューは View です。通常は、これを拡張して新しいスーパー コンポーネントを作成することから始めます。
  • XML から属性とパラメータを取得できるコンストラクタを用意できます。また、VU メーターの色と範囲、針の幅と減衰など、独自の属性とパラメータを使用できます。
  • 独自のイベント リスナー、プロパティ アクセサ、修飾子を作成し、コンポーネント クラスでより高度な動作を作成することをおすすめします。
  • ほとんどの場合、onMeasure() をオーバーライドする必要があります。また、コンポーネントに何かを表示させる場合は、onDraw() もオーバーライドする必要があります。どちらもデフォルトの動作ですが、デフォルトの onDraw() では何も行われず、デフォルトの onMeasure() では常に 100x100 のサイズが設定されますが、これはおそらく望ましいことではありません。
  • 必要に応じて、他の 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) メソッドを呼び出します。これを行わないと、例外が発生します。

フレームワークがビューに対して呼び出すその他の標準メソッドの概要は次のとおりです。

カテゴリ メソッド 説明
作成 コンストラクタ ビューがコードから作成されるときに呼び出される形式のコンストラクタと、ビューがレイアウト ファイルからインフレートされたときに呼び出されるフォームがあります。2 つ目の形式は、レイアウト ファイルで定義されている属性を解析して適用します。
onFinishInflate() ビューとそのすべての子が XML からインフレートされた後に呼び出されます。
レイアウト onMeasure(int, int) このビューとそのすべての子のサイズ要件を指定するために呼び出されます。
onLayout(boolean, int, int, int, int) このビューがそのすべての子にサイズと位置を割り当てる必要がある場合に呼び出されます。
onSizeChanged(int, int, int, int) このビューのサイズが変更されたときに呼び出されます。
図面 onDraw(Canvas) ビューがそのコンテンツをレンダリングする必要がある場合に呼び出されます。
イベント処理 onKeyDown(int, KeyEvent) キーダウン イベントの発生時に呼び出されます。
onKeyUp(int, KeyEvent) キーアップ イベントの発生時に呼び出されます。
onTrackballEvent(MotionEvent) トラックボールのモーション イベントが発生したときに呼び出されます。
onTouchEvent(MotionEvent) タッチスクリーンのモーション イベントが発生したときに呼び出されます。
フォーカス onFocusChanged(boolean, int, Rect) ビューがフォーカスを取得したとき、またはフォーカスを喪失したときに呼び出されます。
onWindowFocusChanged(boolean) ビューを含むウィンドウがフォーカスを取得または失ったときに呼び出されます。
アタッチ onAttachedToWindow() ビューがウィンドウにアタッチされたときに呼び出されます。
onDetachedFromWindow() ビューがウィンドウからデタッチされたときに呼び出されます。
onWindowVisibilityChanged(int) ビューを含むウィンドウの表示状態が変更されたときに呼び出されます。

複合コントロール

完全にカスタマイズされたコンポーネントを作成するのではなく、既存のコントロールのグループで構成される再利用可能なコンポーネントをまとめて配置する場合は、複合コンポーネント(または複合コントロール)を作成することをおすすめします。つまり、よりアトミックな複数のコントロールやビューが、1 つのアイテムとして扱えるアイテムの論理グループにまとめられています。 たとえば、コンボボックスは、単一行の EditText フィールドと、隣接するボタンとポップアップ リストが添付された状態の組み合わせです。ユーザーがボタンをタップしてリストから項目を選択すると、EditText フィールドに値が入力されますが、必要に応じて EditText に直接入力することもできます。

Android では、これを簡単に行うことができるビューが他に SpinnerAutoCompleteTextView あります。いずれにせよ、コンボ ボックスのこのコンセプトは良い例です。

複合コンポーネントを作成する手順は次のとおりです。

  • Activity と同様に、宣言型(XML ベース)のアプローチを使用して、含まれるコンポーネントを作成するか、コードからプログラムでコンポーネントをネストします。通常の開始点はなんらかの Layout であるため、Layout を拡張するクラスを作成します。コンボボックスの場合は、横向きの LinearLayout を使用できます。他のレイアウトを内部にネストできるため、複合コンポーネントを任意の複雑に構造化できます。
  • 新しいクラスのコンストラクタで、スーパークラスが想定しているパラメータを受け取り、それをまずスーパークラスのコンストラクタに渡します。次に、新しいコンポーネント内で使用する他のビューを設定します。ここで、EditText フィールドとポップアップ リストを作成します。コンストラクタが pull して使用できる独自の属性とパラメータを XML に導入することもできます。
  • 必要に応じて、含まれるビューが生成する可能性のあるイベントのリスナーを作成します。たとえば、リストアイテムのクリック リスナーが、リストが選択されたときに EditText のコンテンツを更新するリスナー メソッドがあります。
  • 必要に応じて、アクセサと修飾子を使用して独自のプロパティを作成します。たとえば、コンポーネントで最初に EditText 値を設定し、必要に応じてそのコンテンツをクエリします。
  • 必要に応じて、onDraw()onMeasure() をオーバーライドします。Layout を拡張する場合、レイアウトにはデフォルトの動作があり、問題なく動作する可能性があるため、通常、この操作を行う必要はありません。
  • 必要に応じて、onKeyDown() などの他の on メソッドをオーバーライドします。たとえば、特定のキーがタップされたときに、コンボボックスのポップアップ リストから特定のデフォルト値を選択します。

Layout をカスタム コントロールのベースとして使用することには、次のような利点があります。

  • アクティビティ画面と同様に、宣言型 XML ファイルを使用してレイアウトを指定することも、コードからプログラムでビューを作成してレイアウト内にネストすることもできます。
  • onDraw() メソッドと onMeasure() メソッド、および他のほとんどの on メソッドには適切な動作があるため、これらをオーバーライドする必要はありません。
  • 任意の複雑な複合ビューをすばやく作成して、単一のコンポーネントであるかのように再利用できます。

既存のビュータイプを変更する

必要とするものと似たコンポーネントがある場合は、そのコンポーネントを拡張して、変更する動作をオーバーライドできます。完全にカスタマイズしたコンポーネントでも、すべての作業を行うことができますが、View 階層のより特化されたクラスから開始することで、必要な処理を無料で行うことができます。

たとえば、NotePad サンプルアプリでは、Android プラットフォームの使用方法をさまざまな角度から確認できます。そのうちの 1 つは、EditText ビューを拡張して、枠線付きのメモ帳を作成することです。これは完全な例ではなく、そのための API は変更される可能性がありますが、この原則はあくまでも一例です。

まだの場合は、NotePad サンプルを Android Studio にインポートするか、提供されたリンクを使用してソースを確認します。特に、NoteEditor.java ファイル内の LinedEditText の定義をご覧ください。

このファイルの主なポイントは以下のとおりです。

  1. 定義

    クラスは次の行で定義されます。
    public static class LinedEditText extends EditText

    LinedEditText は、NoteEditor アクティビティ内で内部クラスとして定義されていますが、NoteEditor クラスの外部から NoteEditor.LinedEditText としてアクセスできるよう公開されています。

    また、LinedEditTextstatic です。つまり、親クラスのデータへのアクセスを許可するいわゆる「合成メソッド」は生成しません。つまり、NoteEditor との関連性が強いものではなく、別のクラスとして動作します。これにより、外部クラスから状態にアクセスする必要がない内部クラスをより簡潔に作成できます。生成されるクラスを小さくして、他のクラスから簡単に使用できます。

    LinedEditTextEditText(この場合はカスタマイズするビュー)を拡張します。完了すると、新しいクラスは通常の EditText ビューに置き換えることができます。

  2. クラスの初期化

    通常どおり、先にスーパークラスを呼び出します。これはデフォルトのコンストラクタではありませんが、パラメータ化されたコンストラクタです。EditText は、XML レイアウト ファイルからインフレートするときにこれらのパラメータで作成されます。したがって、コンストラクタもそれらを取得してスーパークラスのコンストラクタに渡す必要があります。

  3. オーバーライドされたメソッド

    この例では onDraw() メソッドのみをオーバーライドしていますが、独自のカスタム コンポーネントを作成するときに、他のメソッドをオーバーライドする必要がある場合があります。

    このサンプルでは、onDraw() メソッドをオーバーライドすることで、EditText ビュー キャンバスに青い線を描画できます。キャンバスは、オーバーライドされた onDraw() メソッドに渡されます。super.onDraw() メソッドは、メソッドが終了する前に呼び出されます。スーパークラス メソッドを呼び出す必要があります。この場合は、含めたい線を描画した後、最後に関数を呼び出します。

  4. カスタム コンポーネント

    これでカスタム コンポーネントが用意できました。では、どのように使用すればよいでしょうか。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 メソッドをオーバーライドして独自のヘルパー メソッドを導入し、プロパティと動作を大幅にカスタマイズできます。想像力を存分に発揮して、コンポーネントに実行させる必要があることに留意してください。