Jetpack Compose を使用すると、アプリの UI の設計と構築が簡単になります。Compose は、次の方法で状態を UI 要素に変換します。
- 要素のコンポジション
- 要素のレイアウト
- 要素の描画
このドキュメントでは、要素のレイアウトに焦点を当て、UI 要素のレイアウトに役立つ Compose のビルディング ブロックについて説明します。
Compose でのレイアウトの目標
Jetpack Compose でのレイアウト システムの実装には、2 つの主な目的があります。
- 高パフォーマンス
- カスタム レイアウトを簡単に記述する機能
コンポーズ可能な関数の基本
コンポーズ可能な関数は、Compose の基本的なビルディング ブロックです。コンポーズ可能な関数とは、UI の一部を記述する Unit
を出力する関数のことです。この関数は入力を受け取り、画面に表示されるものを生成します。コンポーザブルの詳細については、Compose のメンタルモデルについてのドキュメントをご覧ください。
コンポーズ可能な関数は、複数の UI 要素を出力することがあります。ただし、配置方法のガイダンスが提供されないと、Compose は望ましくない方法で要素を配置する可能性があります。たとえば、次のコードは 2 つのテキスト要素を生成します。
@Composable
fun ArtistCard() {
Text("Alfred Sisley")
Text("3 minutes ago")
}
配置方法のガイダンスがないと、Compose はテキスト要素を重ねてしまい、読み取れなくなります。
Compose には、すぐに使用できるレイアウトのコレクションが用意されています。UI 要素の配置に役立ち、より特化した独自のレイアウトを簡単に定義できます。
標準レイアウト コンポーネント
多くの場合、Compose の標準レイアウト要素を使用するだけで済みます。
アイテムを画面上の垂直方向に配置するには、Column
を使用します。
@Composable
fun ArtistCard() {
Column {
Text("Alfred Sisley")
Text("3 minutes ago")
}
}
同様に、アイテムを画面上の水平方向に配置するには、Row
を使用します。Column
と Row
はどちらも、含まれる要素の配置の構成をサポートしています。
@Composable
fun ArtistCard(artist: Artist) {
Row(verticalAlignment = Alignment.CenterVertically) {
Image(/*...*/)
Column {
Text(artist.name)
Text(artist.lastSeenOnline)
}
}
}
要素を別の要素の上に配置するには、Box
を使用します。Box
では、それに含まれる要素の特定の配置を設定することも可能です。
@Composable
fun ArtistAvatar(artist: Artist) {
Box {
Image(/*...*/)
Icon(/*...*/)
}
}
ほとんどの場合、これらのビルディング ブロックだけで済みます。独自のコンポーズ可能な関数を作成することで、こうしたレイアウトを組み合わせ、アプリに適した、より手の込んだレイアウトにできます。
Row
内の子の位置を設定するには、horizontalArrangement
引数と verticalAlignment
引数を設定します。Column
の場合は、verticalArrangement
引数と horizontalAlignment
引数を設定します。
@Composable
fun ArtistCard(artist: Artist) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.End
) {
Image(/*...*/)
Column { /*...*/ }
}
}
レイアウト モデル
レイアウト モデルでは、UI ツリーが 1 つのパスでレイアウトされます。各ノードはまず自身を測定するよう求められ、次にすべての子を再帰的に測定して、サイズ制約をツリー上の子ノードに渡します。その後、リーフノードのサイズと配置が設定され、解決されたサイズと配置の指示がツリーに戻されます。
簡単に言うと、親は子より前に測定しますが、サイズと配置の設定は子より後です。
次の SearchResult
関数について考えてみましょう。
@Composable
fun SearchResult(...) {
Row(...) {
Image(...)
Column(...) {
Text(...)
Text(..)
}
}
}
この関数は、次の UI ツリーを生成します。
SearchResult
Row
Image
Column
Text
Text
SearchResult
の例では、UI ツリー レイアウトはこの順序に従います。
- ルートノード
Row
が測定するように求められます。 - ルートノード
Row
は、最初の子Image
に測定するよう求めます。 Image
はリーフノードである(つまり子がない)ため、サイズを報告し、配置指示を返します。- ルートノード
Row
は、2 番目の子Column
に測定するよう求めます。 Column
ノードは、最初の子Text
に測定するよう求めます。- 最初の
Text
ノードはリーフノードであるため、サイズを報告し、配置指示を返します。 Column
ノードは、2 番目の子Text
に測定するよう求めます。- 2 番目の
Text
ノードはリーフノードであるため、サイズを報告し、配置指示を返します。 Column
ノードが子を測定し、サイズを設定して、配置したため、自身のサイズと配置を決定できます。- ルートノード
Row
が子を測定し、サイズを設定して、配置したため、自身のサイズと配置を決定できます。
パフォーマンス
Compose は、子を一度測定するだけで高いパフォーマンスを実現します。シングルパス測定はパフォーマンスに優れており、Compose は深い UI ツリーを効率的に処理できます。要素が子を 2 回測定し、その子が自身の子をそれぞれ 2 回測定した場合など、UI 全体をレイアウトしようとすると多くの作業が必要になるため、アプリのパフォーマンスを保つことが難しくなります。
なんらかの理由でレイアウトで複数回の測定が必要な場合、Compose には固有の測定値という特別なシステムが用意されています。この機能の詳細については、Compose レイアウトの固有の測定値をご覧ください。
測定と配置はレイアウトパスの異なるサブフェーズであるため、アイテムの配置にのみ影響し測定に影響しない変更は別々に実行できます。
レイアウトでの修飾子の使用
Compose 修飾子で説明したように、修飾子を使用すると、コンポーザブルを装飾または拡張できます。修飾子は、レイアウトをカスタマイズするうえで不可欠です。たとえば、以下では複数の修飾子を連結して ArtistCard
をカスタマイズしています。
@Composable
fun ArtistCard(
artist: Artist,
onClick: () -> Unit
) {
val padding = 16.dp
Column(
Modifier
.clickable(onClick = onClick)
.padding(padding)
.fillMaxWidth()
) {
Row(verticalAlignment = Alignment.CenterVertically) { /*...*/ }
Spacer(Modifier.size(padding))
Card(elevation = 4.dp) { /*...*/ }
}
}
上記のコードでは、さまざまな修飾子関数を一緒に使用しています。
clickable
は、コンポーザブルをユーザー入力に反応させ、波紋を表示します。padding
は、要素の周囲にスペースを挿入します。fillMaxWidth
は、コンポーザブルを親から与えられた最大幅に合わせて調整します。size()
は、要素の優先的な幅と高さを指定します。
スクロール可能なレイアウト
スクロール可能なレイアウトについて詳しくは、Compose の操作のドキュメントをご覧ください。
リストと遅延リストについては、Compose リストのドキュメントをご覧ください。
レスポンシブ レイアウト
レイアウトは、さまざまな画面の向きとフォーム ファクタのサイズを考慮して設計する必要があります。Compose には、コンポーザブルのレイアウトをさまざまな画面構成に簡単に適応させることができるように、すぐに使えるメカニズムがいくつか用意されています。
制約
親の制約を把握し、それに応じてレイアウトを設計するには、BoxWithConstraints
を使用します。測定の制約は、コンテンツ ラムダのスコープにあります。これらの測定の制約を使用して、画面構成ごとに異なるレイアウトをコンポーズできます。
@Composable
fun WithConstraintsComposable() {
BoxWithConstraints {
Text("My minHeight is $minHeight while my maxWidth is $maxWidth")
}
}
スロットベースのレイアウト
Compose では、androidx.compose.material:material
の依存関係(Android Studio で Compose プロジェクトを作成するときに組み込まれる)によって、マテリアル デザインをベースにしたさまざまなコンポーザブルが提供されるため、簡単に UI を作成できます。Drawer
、FloatingActionButton
、TopAppBar
などの要素がすべて提供されます。
マテリアル コンポーネントはスロット API を多用します。これは、コンポーザブルの上にカスタマイズのレイヤを適用するために Compose で導入されたパターンです。このアプローチでは、自身を構成できる子要素をコンポーネントが受け入れるため、子のすべての構成パラメータを公開する必要がなく、より柔軟にコンポーネントを利用できます。スロットは UI に空のスペースを残し、デベロッパーが自由に使用できるようにします。たとえば、TopAppBar
でカスタマイズできるスロットは次のとおりです。
コンポーザブルは通常、content
コンポーザブル ラムダ(content: @Composable
() -> Unit
)を取ります。スロット API は、特定の用途のために複数の content
パラメータを公開します。たとえば、TopAppBar
を使用すると、title
、navigationIcon
、actions
のコンテンツを提供できます。
Scaffold
を使用すると、基本的なマテリアル デザインのレイアウト構造で UI を実装できます。Scaffold
には、TopAppBar
、BottomAppBar
、FloatingActionButton
、Drawer
など、最も一般的なトップレベルのマテリアル コンポーネント向けのスロットが用意されています。Scaffold
を使用すると、こうしたコンポーネントを適切に配置し、ともに正しく動作させることが簡単になります。
@Composable
fun HomeScreen(/*...*/) {
Scaffold(
drawerContent = { /*...*/ },
topBar = { /*...*/ },
content = { /*...*/ }
)
}