ビューのレイアウト

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 オブジェクトを作成し、そのプロパティをプログラムで操作できます。

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

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

XML の記述

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

各レイアウト ファイルには、ルート要素を 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 オブジェクトに共通です。その他の属性はレイアウト パラメータと見なされます。レイアウト パラメータは、Viewオブジェクトの親オブジェクトによって定義されるViewGroupオブジェクトの特定のレイアウト方向を記述する属性です。

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 のサブクラスを使用できます。AdapterView よりもメモリを効率的に使用する RecyclerView の方が一般的に適切なオプションです。

RecyclerViewAdapterView で使用できる一般的なレイアウトは次のとおりです。

リスト

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

グリッド

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

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

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

AdapterView インスタンスを Adapter にバインドすることで、ListViewGridView などの AdapterView にデータを入力できます。これにより、外部ソースからデータを取得し、各データエントリを表す 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 デモアプリで、レイアウトの使用方法をご覧ください。