View でのレイアウト

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

レイアウトは、アクティビティなど、アプリのユーザー インターフェースの構造を定義します。レイアウト内のすべての要素は、View オブジェクトと ViewGroup オブジェクトの階層を使用して作成されます。View は通常、ユーザーが見て操作できるものを描画します。図 1 に示すように、ViewGroupView などの ViewGroup オブジェクトのレイアウト構造を定義する非表示のコンテナです。

図 1. UI レイアウトを定義するビュー階層の図

View オブジェクトはウィジェットと呼ばれることが多く、ButtonTextView など、さまざまなサブクラスのいずれかになります。通常、ViewGroup オブジェクトは「レイアウト」と呼ばれ、LinearLayoutConstraintLayout など、異なるレイアウト構造を提供する多くのタイプのいずれかになります。

レイアウトを宣言する方法は 2 通りあります。

  • XML で UI 要素を宣言する。Android には、ウィジェットやレイアウト用のものなど、View のクラスとサブクラスに対応するシンプルな XML ボキャブラリが用意されています。また、Android Studio の Layout Editor を使用して、ドラッグ&ドロップ インターフェースを使用して XML レイアウトを作成することもできます。

  • 実行時にレイアウト要素をインスタンス化します。アプリでは、View オブジェクトと ViewGroup オブジェクトを作成し、それらのプロパティをプログラムで操作できます。

UI を XML で宣言すると、アプリの表示を、その動作を制御するコードから分離できます。また、XML ファイルを使用すると、画面サイズと画面の向きごとに異なるレイアウトを簡単に提供できます。詳細については、各種の画面サイズのサポートをご覧ください。

Android フレームワークでは、このいずれかまたは両方のメソッドを使用してアプリの UI を柔軟にビルドできます。たとえば、アプリのデフォルト レイアウトを XML で宣言して、そのレイアウトを実行時に変更できます。

XML の記述

Android の XML ボキャブラリを使用すると、UI レイアウトとそれに含まれる画面要素を、一連のネストされた要素を使用した HTML でウェブページを作成するのと同様に迅速に設計できます。

各レイアウト ファイルには、ルート要素を 1 つだけ含める必要があります。ルート要素は View オブジェクトまたは ViewGroup オブジェクトである必要があります。ルート要素を定義したら、別のレイアウト オブジェクトまたはウィジェットを子要素として追加して、レイアウトを定義する View 階層を段階的に構築できます。以下に、縦長の LinearLayout を使用して TextViewButton を保持する XML レイアウトの例を示します。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical" >
    <TextView android:id="@+id/text"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="Hello, I am a TextView" />
    <Button android:id="@+id/button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello, I am a Button" />
</LinearLayout>

XML でレイアウトを宣言したら、適切にコンパイルされるように、.xml 拡張子を付けて Android プロジェクトの res/layout/ ディレクトリにファイルを保存します。

レイアウト XML ファイルの構文の詳細については、レイアウト リソースをご覧ください。

XML リソースを読み込む

アプリをコンパイルすると、各 XML レイアウト ファイルは View リソースにコンパイルされます。アプリの Activity.onCreate() コールバックの実装でレイアウト リソースを読み込みます。そのためには、setContentView() を呼び出し、R.layout.layout_file_name の形式でレイアウト リソースへの参照を渡します。たとえば、XML レイアウトが main_layout.xml として保存されている場合は、次のように Activity で読み込みます。

Kotlin

fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main_layout)
}

Java

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_layout);
}

Android フレームワークは、Activity の起動時に Activity 内の onCreate() コールバック メソッドを呼び出します。アクティビティのライフサイクルの詳細については、アクティビティの概要をご覧ください。

属性

すべての View オブジェクトと ViewGroup オブジェクトは、それぞれ独自の XML 属性をサポートします。一部の属性は View オブジェクトに固有のものです。たとえば、TextViewtextSize 属性をサポートしています。ただし、これらの属性は、このクラスを拡張する View オブジェクトにも継承されます。一部は id 属性など、ルートの View クラスから継承されるため、すべての View オブジェクトに共通します。その他の属性はレイアウト パラメータと見なされます。これは、そのオブジェクトの親の ViewGroup オブジェクトによって定義される、View オブジェクトの特定のレイアウト向きを記述する属性です。

ID

ツリー内の View を一意に識別するために、どの View オブジェクトにも整数 ID を関連付けることができます。この ID は、アプリのコンパイル時に整数として参照されますが、この ID は通常、レイアウト XML ファイルの id 属性の文字列として割り当てられます。これは、すべての View オブジェクトに共通の XML 属性であり、View クラスで定義されます。頻繁に使用します。XML タグ内の ID の構文は次のとおりです。

android:id="@+id/my_button"

文字列の先頭にある at 記号(@)は、XML パーサーが ID 文字列の残りの部分を解析して展開し、それを ID リソースとして識別することを示します。プラス記号(+)は、R.java ファイルで作成しリソースに追加する必要がある新しいリソース名であることを意味します。

Android フレームワークには、他にも多くの ID リソースが用意されています。Android リソース ID を参照する場合、プラス記号は不要ですが、次のように android パッケージの名前空間を追加する必要があります。

android:id="@android:id/empty"

android パッケージの名前空間は、ローカル リソース クラスではなく、android.R リソースクラスの ID を参照していることを示します。

ビューを作成してアプリから参照するには、次のような一般的なパターンを使用します。

  1. 次の例に示すように、レイアウト ファイル内でビューを定義して一意の ID を割り当てます。
    <Button android:id="@+id/my_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/my_button_text"/>
    
  2. ビュー オブジェクトのインスタンスを作成し、レイアウトからキャプチャします。通常、次の例に示すように onCreate() メソッドを使用します。

    Kotlin

    val myButton: Button = findViewById(R.id.my_button)
    

    Java

    Button myButton = (Button) findViewById(R.id.my_button);
    

RelativeLayout を作成する際は、ビュー オブジェクトの ID を定義することが重要です。相対レイアウトでは、兄弟ビューは、一意の ID で参照される別の兄弟ビューを基準としてレイアウトを定義できます。

ID はツリー全体で一意である必要はありませんが、検索するツリーの部分内で一意である必要があります。通常はツリー全体であるため、可能であれば一意にすることをおすすめします。

レイアウト パラメータ

layout_something という名前の XML レイアウト属性は、それが存在する ViewGroup に適した View のレイアウト パラメータを定義します。

すべての ViewGroup クラスは、ViewGroup.LayoutParams を拡張するネストされたクラスを実装します。このサブクラスには、ビューグループに応じて、各子ビューのサイズと位置を定義するプロパティ型が含まれます。図 2 に示すように、親ビューグループは、子ビューグループを含む各子ビューのレイアウト パラメータを定義します。

図 2. 各ビューに関連付けられたレイアウト パラメータを含むビュー階層の可視化

すべての LayoutParams サブクラスには、値を設定するための独自の構文があります。各子要素は、その親に適した LayoutParams を定義する必要がありますが、自身の子に異なる LayoutParams を定義することもできます。

すべてのビューグループには、layout_widthlayout_height を使用して幅と高さが含まれます。これらのグループは各ビューで定義する必要があります。多くの LayoutParams にはオプションのマージンと枠線が含まれています。

幅と高さは正確な単位で指定できますが、頻繁に指定しないでください。多くの場合、次のいずれかの定数を使用して幅または高さを設定します。

  • wrap_content: ビューのサイズを、そのコンテンツに必要なサイズに設定します。
  • match_parent: ビューを親ビューグループで許容される最大サイズにします。

一般に、ピクセルなどの絶対単位でレイアウトの幅と高さを指定することはおすすめしません。密度非依存ピクセル単位(dp)、wrap_contentmatch_parent などの相対測定値を使用すると、さまざまなデバイスの画面サイズでアプリを適切に表示できるため、この方法をおすすめします。使用可能な測定タイプは、レイアウト リソースで定義されています。

レイアウトの位置

ビューのジオメトリは長方形です。位置は、左と上のペア座標と上座標のペアで表し、2 次元は幅と高さで表されます。位置と寸法の単位はピクセルです。

ビューのロケーションを取得するには、getLeft() メソッドと getTop() メソッドを呼び出します。前者はビューを表す長方形の左(x)座標を返します。後者はビューを表す長方形のトップ(y)座標を返します。これらのメソッドは、親に対する相対的なビューの位置を返します。たとえば、getLeft() が 20 を返す場合、ビューは直接親の左端から右 20 ピクセルにあります。

さらに、不要な計算を回避するための便利なメソッドとして、getRight()getBottom() があります。これらのメソッドは、ビューを表す長方形の右端と下端の座標を返します。たとえば、getRight() の呼び出しは、getLeft() + getWidth() の計算に似ています。

サイズ、パディング、余白

ビューのサイズは、幅と高さで表されます。ビューには、幅と高さの値のペアが 2 つあります。

最初のペアは、測定幅と測定高さと呼ばれます。これらのディメンションは、親内のビューのサイズを定義します。測定した寸法を取得するには、getMeasuredWidth()getMeasuredHeight() を呼び出します。

2 つ目のペアは、幅と高さ、または描画幅と描画高さとも呼ばれます。これらのディメンションは、画面上、描画時、レイアウト後のビューの実際のサイズを定義します。これらの値は、測定される幅と高さと異なる場合がありますが、必須ではありません。幅と高さを取得するには、getWidth()getHeight() を呼び出します。

寸法を測定するために、ビューはパディングを考慮します。パディングは、ビューの左、上、右、下の部分のピクセル数で表されます。パディングを使用して、特定のピクセル数だけビューのコンテンツをオフセットできます。たとえば、左パディングを 2 にすると、ビューのコンテンツが左端から 2 ピクセル右側に押し出されます。setPadding(int, int, int, int) メソッドを使用してパディングを設定し、getPaddingLeft()getPaddingTop()getPaddingRight()getPaddingBottom() を呼び出してクエリを行うことができます。

ビューではパディングを定義できますが、マージンはサポートされていません。ただし、ビューグループはマージンをサポートしています。詳細については、ViewGroupViewGroup.MarginLayoutParams をご覧ください。

ディメンションの詳細については、ディメンションをご覧ください。

マージンとパディングをプログラムで設定する以外に、XML レイアウトで設定することもできます。次の例をご覧ください。

  <?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical" >
      <TextView android:id="@+id/text"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="16dp"
                android:padding="8dp"
                android:text="Hello, I am a TextView" />
      <Button android:id="@+id/button"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_marginTop="16dp"
              android:paddingBottom="4dp"
              android:paddingEnd="8dp"
              android:paddingStart="8dp"
              android:paddingTop="4dp"
              android:text="Hello, I am a Button" />
  </LinearLayout>
  

上記の例は、マージンとパディングが適用されていることを示しています。TextView では周囲に均一なマージンとパディングが適用されています。Button では、これらをさまざまなエッジに独立して適用する方法が示されています。

一般的なレイアウト

ViewGroup クラスの各サブクラスには、内部にネストされたビューを表示する独自の方法が用意されています。最も柔軟なレイアウト タイプで、レイアウト階層を浅く維持するための最適なツールを提供するのは ConstraintLayout です。

Android プラットフォームに組み込まれている一般的なレイアウト タイプの一部を次に示します。

線形レイアウトを作成する

子を水平方向または垂直方向の 1 行に整理し、ウィンドウの長さが画面の長さを超えた場合にスクロールバーを作成します。

WebView でウェブアプリを作成する

ウェブページを表示します。

動的リストを作成する

レイアウトのコンテンツが動的である場合や、事前に決定されていない場合は、RecyclerView または AdapterView のサブクラスを使用できます。RecyclerView の方が AdapterView よりメモリを効率的に使用するため、通常はより適切なオプションです。

RecyclerViewAdapterView で可能な一般的なレイアウトは次のとおりです。

リスト

スクロール可能な 1 列のリストが表示されます。

グリッド

スクロール可能な列と行のグリッドが表示されます。

RecyclerView は、可能性を広げ、カスタム レイアウト マネージャーを作成するオプションを提供します。

アダプタビューにデータを入力する

AdapterView インスタンスを Adapter にバインドすることで、ListViewGridView などの AdapterView にデータを設定できます。Adapter は外部ソースからデータを取得して、各データエントリを表す View を作成します。

Android には、さまざまな種類のデータの取得や AdapterView のビューの作成に役立つ Adapter のサブクラスがいくつかあります。最も一般的なアダプタは、次の 2 つです。

ArrayAdapter
データソースが配列の場合は、このアダプターを使用します。デフォルトでは、ArrayAdapter は各アイテムに対して toString() を呼び出してコンテンツを TextView に配置することにより、各配列アイテムのビューを作成します。

たとえば、ListView に表示する文字列の配列がある場合、コンストラクタを使用して新しい ArrayAdapter を初期化して、各文字列と文字列配列のレイアウトを指定します。

Kotlin

    val adapter = ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, myStringArray)
    

Java

    ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
            android.R.layout.simple_list_item_1, myStringArray);
    

このコンストラクタの引数は次のとおりです。

  • アプリ Context
  • 配列内の各文字列の TextView を含むレイアウト
  • 文字列配列

次に、ListViewsetAdapter() を呼び出します。

Kotlin

    val listView: ListView = findViewById(R.id.listview)
    listView.adapter = adapter
    

Java

    ListView listView = (ListView) findViewById(R.id.listview);
    listView.setAdapter(adapter);
    

各アイテムの外観をカスタマイズするには、配列内のオブジェクトの toString() メソッドをオーバーライドします。または、TextView 以外のアイテムごとにビューを作成するには(たとえば、各配列アイテムに ImageView が必要な場合)、ArrayAdapter クラスを拡張して getView() をオーバーライドして、各アイテムに必要なビューのタイプを返します。

SimpleCursorAdapter
データが Cursor から取得されている場合は、このアダプターを使用します。 SimpleCursorAdapter を使用する場合は、Cursor の各行に使用するレイアウトと、レイアウトのビューに挿入する Cursor の列を指定します。たとえば、人の名前と電話番号のリストを作成する場合、各人の行と、名前と電話番号の列を含む Cursor を返すクエリを実行できます。次に、各結果のレイアウトに含める Cursor の列を指定する文字列配列と、各列が配置する必要がある対応するビューを指定する整数配列を作成します。

Kotlin

    val fromColumns = arrayOf(ContactsContract.Data.DISPLAY_NAME,
                              ContactsContract.CommonDataKinds.Phone.NUMBER)
    val toViews = intArrayOf(R.id.display_name, R.id.phone_number)
    

Java

    String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME,
                            ContactsContract.CommonDataKinds.Phone.NUMBER};
    int[] toViews = {R.id.display_name, R.id.phone_number};
    

SimpleCursorAdapter をインスタンス化する際に、各結果に使用するレイアウト、結果を格納する Cursor、および次の 2 つの配列を渡します。

Kotlin

    val adapter = SimpleCursorAdapter(this,
            R.layout.person_name_and_number, cursor, fromColumns, toViews, 0)
    val listView = getListView()
    listView.adapter = adapter
    

Java

    SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
            R.layout.person_name_and_number, cursor, fromColumns, toViews, 0);
    ListView listView = getListView();
    listView.setAdapter(adapter);
    

次に SimpleCursorAdapter は、各 fromColumns アイテムを対応する toViews ビューに挿入して、指定されたレイアウトを使用して Cursor の各行のビューを作成します。

アプリの実行中に、アダプタによって読み取られる基となるデータを変更する場合は、notifyDataSetChanged() を呼び出します。これにより、アタッチされたビューにデータが変更されたことが通知され、ビュー自体が更新されます。

クリック イベントを処理する

AdapterView の各アイテムのクリック イベントに応答するには、AdapterView.OnItemClickListener インターフェースを実装します。次に例を示します。

Kotlin

listView.onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
    // Do something in response to the click.
}

Java

// Create a message handling object as an anonymous class.
private OnItemClickListener messageClickedHandler = new OnItemClickListener() {
    public void onItemClick(AdapterView parent, View v, int position, long id) {
        // Do something in response to the click.
    }
};

listView.setOnItemClickListener(messageClickedHandler);

参考情報

GitHub の Sunflower デモアプリで、レイアウトの使用方法をご覧ください。