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

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

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

利用可能なウィジェットとしては、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 つにまとめる場合は、複合コンポーネント(複合コントロール)を作成することをおすすめします。まとめると、多数のアトミックなコントロールやビューがアイテムの論理グループにまとめられ、1 つのものとして扱うことができます。 たとえば、コンボボックスは、単一行の EditText フィールドと、ポップアップ リストが接続された隣接するボタンの組み合わせにできます。ユーザーがボタンをタップしてリストから何かを選択すると、EditText フィールドに値が設定されますが、必要に応じて EditText に直接入力することもできます。

Android では、これ以外に SpinnerAutoCompleteTextView の 2 つのビューをすぐに使用できます。いずれにせよ、このコンボボックスのコンセプトは良い例となります。

複合コンポーネントを作成するには、次の操作を行います。

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

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

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

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

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

たとえば、NotePad サンプルアプリは、Android プラットフォームを使用するさまざまな側面を示しています。その中には、EditText ビューを拡張して枠線付きのメモ帳を作成する手法もあります。これは完璧な例ではなく、これを行う API は変更される可能性がありますが、原則を示しています。

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

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

  1. 定義

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

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

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

    LinedEditText は、EditText(この場合はカスタマイズするビュー)を拡張します。完了すると、新しいクラスで通常の 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 メソッドをオーバーライドし、独自のヘルパー メソッドを導入して、プロパティと動作を大幅にカスタマイズできます。お客様の想像力とコンポーネントで行う必要がある処理が唯一の限界となります。