🙄

Jetpack Compose のレイアウトまとめ3: 時々使う位置調整

2023/09/18に公開

今回は特定のケースで必要になるような配置に関する機能について説明します。より基本的な内容は以下に書いています。

https://zenn.dev/wm3/articles/f7393d910fa312
https://zenn.dev/wm3/articles/7332788c626b39

アスペクト比による指定

Modifier.aspectRatio() を使う事でアスペクト比を指定する事ができます。

Box(Modifier.aspectRatio(2f))

実行結果
アスペクト比2:1で指定した場合の制約の変化

aspectRatio はアスペクト比を維持できる限り制約の下限を最大まで広げます。従って通常は fillMaxWidth() などを同時にセットする必要はありません。

テキストが収まるいい感じのサイズで揃える (Intrinsic)

いくつかの操作を提供するポップアップメニューを作る事を考えます。シンプルには以下のようになりそうです[1]

Surface(elevation = 4.dp) {
    Column {
        Text("Copy", Modifier.clickable { /* … */ }.padding(8.dp))
        Text("Paste️", Modifier.clickable { /* … */ }.padding(8.dp))
        Text("Select All", Modifier.clickable { /* … */ }.padding(8.dp))
    }
}

しかしこれではうまくいきません。クリック領域がテキストを含む最小限の領域になってしまい、右端まで広がってくれません。

実行結果
そうじゃない

では幅を広げるために Column の各要素に Modifier.fillMaxWidth() を使ってみます。

Surface(elevation = 4.dp) {
    Column {
        Text("Copy", Modifier.fillMaxWidth().clickable { /* … */ }.padding(8.dp))
        Text("Paste️", Modifier.fillMaxWidth().clickable { /* … */ }.padding(8.dp))
        Text("Select All", Modifier.fillMaxWidth().clickable { /* … */ }.padding(8.dp))
    }
}

しかしこれもうまくいきません。fillMaxWidth() により制約の最小値が最大まで広げられてしまったため、表示可能領域の最大までメニューが広がってしまいました(ピンと来なかった人は第一回を観ながら考えてみると良いです)。

実行結果
そうじゃない!!

そこで、ColumnModifier.width(IntrinsicSize.Max) を加えます。

Surface(elevation = 4.dp) {
    Column(Modifier.width(IntrinsicSize.Max)) {
        Text("Copy", Modifier.fillMaxWidth().clickable { /* … */ }.padding(8.dp))
        Text("Paste️️", Modifier.fillMaxWidth().clickable { /* … */ }.padding(8.dp))
        Text("Select All", Modifier.fillMaxWidth().clickable { /* … */ }.padding(8.dp))
    }
}

すると、メニューの幅がテキストを表示できる最小幅まで収まる一方で、クリック領域は領域内の最大幅まで広がってくれます。

実行結果
そう

Modifier.width(IntrinsicSize.〜) の意味

Modifier.width(IntrinsicSize.〜) を使う事で、コンポーネントがちょうど収まる最大(もしくは最小)の幅を求めるためにレイアウト計算した後、その値で制約を使って再度レイアウトが行われます。

この際、最初のレイアウト計算では size() などの一部の制約のみが考慮され、fillMaxSize() などの制約は無視されます[2]

IntrinsicWize.〜 で設定した結果は以下のようになります。

意味 テキストでの実行結果
Modifier.width(IntrinsicSize.Min)
コンポーネントが収まる最小幅

最長の単語の幅になる
Modifier.width(IntrinsicSize.Max)
コンポーネントが収まる最大幅

テキストの場合はテキスト全体の幅になる。入り切らなければ折り返される。
Modifier.height(IntrinsicSize.Max)
Modifier.height(IntrinsicSize.Min)

コンポーネントが収まる最大・最小高さ

テキストの場合は各単語の最大幅になる

サイズによってレイアウトの仕方を変える

要素のサイズに応じてレイアウトを変える方法はいくつかありますが、ここでは BoxWithConstraints を使った方法を紹介します。BoxWithConstraints を使うと制約の情報を情報を受け取る事が出来ます。

BoxWithConstraints(Modifier.width(width.dp)) {
    // 幅の最小・最大を取得
    val min = minWidth
    val max = maxWidth
}

これによって、例えば幅に応じて weight をつけるコンポーネントを変える事が出来ます。

BoxWithConstraints(Modifier.width(width.dp)) {
    // 制約から幅の上限を取得し、160dp 以下かどうかを判別
    val small = maxWidth < 160.dp
    Row {
        // 160dp 以下であれば最初の要素の幅を可変にする
        // それ以上であれば二つ目の要素の幅を可変にする
        val widthModifier1 = if (small) Modifier.weight(1f) else Modifier.width(80.dp)
        val widthModifier2 = if (small) Modifier.width(80.dp) else Modifier.weight(1f)
        Box(widthModifier1.height(40.dp).inspect(Color.Blue))
        Box(widthModifier2.height(40.dp).inspect(Color.Red))
    }
}

実行結果

Row のベースライン揃え

さまざまなサイズのテキストをベースラインで揃えたい場合、Modifier.alignBy() Modifier.alignByBaseline() を使います。

Row {
    Text("Android", Modifier.alignByBaseline(), fontSize = 32.sp)
    Text("❤️", Modifier.alignByBaseline(), fontSize = 24.sp)
    Text("Jetpack\nCompose", Modifier.alignByBaseline())
}
設定 結果
なし
Modifier.alignByBaseline()
1行目のベースラインで揃える
Modifier.alignBy(LastBaseline)
最終行のベースラインで揃える
脚注
  1. 実際にはポップアップメニューは Material Compose で DropdownMenu として提供されています。通常は今回のように自前でメニューを用意する必要はありません。 ↩︎

  2. Intrinsic 計算時の制約を定義するカスタムの Modifier やコンポーネントを作る事も可能です。こちらに書かれてあります↩︎

Discussion