Compose の Flow レイアウト

FlowRowFlowColumn は、RowColumn に似たコンポーザブルですが、コンテナのスペースが不足するとアイテムが次の行に流れ込む点が異なります。これにより、複数の行または列が作成されます。1 行内のアイテム数は、maxItemsInEachRow または maxItemsInEachColumn を設定することで制御することもできます。多くの場合、FlowRowFlowColumn を使用してレスポンシブ レイアウトを作成できます。アイテムが 1 つのディメンションに収まらない場合でも、コンテンツが切り捨てられることはありません。maxItemsInEach*Modifier.weight(weight) を組み合わせて使用すると、必要に応じて行または列の幅を拡張するレイアウトを作成できます。

一般的な例としては、チップやフィルタリング UI があります。

FlowRow 内の 5 つのチップ。使用可能なスペースがなくなったときに、次の行にオーバーフローします。
図 1. FlowRow の例

基本的な使用方法

FlowRow または FlowColumn を使用するには、これらのコンポーザブルを作成し、標準フローに従うアイテムをその中に配置します。

@Composable
private fun FlowRowSimpleUsageExample() {
    FlowRow(modifier = Modifier.padding(8.dp)) {
        ChipItem("Price: High to Low")
        ChipItem("Avg rating: 4+")
        ChipItem("Free breakfast")
        ChipItem("Free cancellation")
        ChipItem("£50 pn")
    }
}

このスニペットを使用すると、上記の UI が生成され、最初の行にスペースがなくなったときにアイテムが自動的に次の行に移動します。

フロー レイアウトの特徴

フロー レイアウトには、アプリでさまざまなレイアウトを作成するために使用できる次の機能とプロパティがあります。

メイン軸の配置: 水平方向または垂直方向

メイン軸は、アイテムが配置される軸です(たとえば、FlowRow ではアイテムが水平方向に配置されます)。FlowRowhorizontalArrangement パラメータは、空き領域がアイテム間で分散される方法を制御します。

次の表に、FlowRow のアイテムに horizontalArrangement を設定する例を示します。

水平配置が FlowRow に設定されている

結果

Arrangement.StartDefault

開始日で並べ替えたアイテム

Arrangement.SpaceBetween

アイテムを間隔を空けて配置する

Arrangement.Center

中央に配置されたアイテム

Arrangement.End

アイテムが最後に配置される

Arrangement.SpaceAround

アイテムの周囲にスペースを空けて配置する

Arrangement.spacedBy(8.dp)

アイテムが特定の dp 間隔で配置されている

FlowColumn の場合、verticalArrangement で同様のオプションを使用できます。デフォルトは Arrangement.Top です。

軸をまたぐ配置

交差軸は、メイン軸の反対方向の軸です。たとえば、FlowRow では垂直軸です。コンテナ内の全体的なコンテンツをクロス軸に配置する方法は、FlowRowverticalArrangementFlowColumnhorizontalArrangement を使用することで変更できます。

FlowRow の場合、次の表に、アイテムにさまざまな verticalArrangement を設定する例を示します。

垂直方向の配置が FlowRow に設定されている

結果

Arrangement.TopDefault

コンテナの上部配置

Arrangement.Bottom

コンテナの底部配置

Arrangement.Center

コンテナの中央配置

FlowColumn の場合、horizontalArrangement で同様のオプションを使用できます。デフォルトのクロス軸配置は Arrangement.Start です。

個々のアイテムの調整

行内の個々の項目を異なる配置で配置することもできます。これは、項目を現在の行内に配置するため、verticalArrangementhorizontalArrangement とは異なります。これは Modifier.align() で適用できます。

たとえば、FlowRow 内のアイテムの高さが異なる場合、行は最大のアイテムの高さを取り、アイテムに Modifier.align(alignmentOption) を適用します。

垂直方向の配置が FlowRow に設定されている

結果

Alignment.TopDefault

アイテムを上揃え

Alignment.Bottom

アイテムが下揃えになっている

Alignment.CenterVertically

アイテムが中央揃えになっている

FlowColumn には、同様のオプションが用意されています。デフォルトの配置は Alignment.Start です。

行または列の最大アイテム数

パラメータ maxItemsInEachRow または maxItemsInEachColumn は、メイン軸で 1 行に表示できるアイテムの最大数を定義します。この数を超えると、次の行に折り返されます。デフォルトは Int.MAX_INT で、サイズが行に収まる限り、できるだけ多くのアイテムを指定できます。

たとえば、maxItemsInEachRow を設定すると、初期レイアウトに 3 つのアイテムのみが強制的に配置されます。

上限なし

maxItemsInEachRow = 3

フロー行に最大値が設定されていない フロー行に設定された最大アイテム数

フローアイテムの遅延読み込み

ContextualFlowRowContextualFlowColumn は、FlowRowFlowColumn の特殊なバージョンで、フロー行または列のコンテンツを遅延読み込みできます。また、アイテムが最初の行にあるかどうかなど、アイテムの位置(インデックス、行番号、使用可能なサイズ)に関する情報も提供します。これは、大規模なデータセットや、アイテムのコンテキスト情報を必要とする場合に便利です。

maxLines パラメータは表示する行数を制限し、overflow パラメータはアイテムのオーバーフロー時に表示する内容を指定します。これにより、カスタムの expandIndicator または collapseIndicator を指定できます。

たとえば、「+(残りのアイテム数)」ボタンや「表示を減らす」ボタンを表示するには、次のようにします。

val totalCount = 40
var maxLines by remember {
    mutableStateOf(2)
}

val moreOrCollapseIndicator = @Composable { scope: ContextualFlowRowOverflowScope ->
    val remainingItems = totalCount - scope.shownItemCount
    ChipItem(if (remainingItems == 0) "Less" else "+$remainingItems", onClick = {
        if (remainingItems == 0) {
            maxLines = 2
        } else {
            maxLines += 5
        }
    })
}
ContextualFlowRow(
    modifier = Modifier
        .safeDrawingPadding()
        .fillMaxWidth(1f)
        .padding(16.dp)
        .wrapContentHeight(align = Alignment.Top)
        .verticalScroll(rememberScrollState()),
    verticalArrangement = Arrangement.spacedBy(4.dp),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    maxLines = maxLines,
    overflow = ContextualFlowRowOverflow.expandOrCollapseIndicator(
        minRowsToShowCollapse = 4,
        expandIndicator = moreOrCollapseIndicator,
        collapseIndicator = moreOrCollapseIndicator
    ),
    itemCount = totalCount
) { index ->
    ChipItem("Item $index")
}

コンテキスト フロー行の例。
図 2. ContextualFlowRow の例

商品の重量

重み付けは、係数と、配置された行の空きスペースに基づいてアイテムを拡大します。重要な点として、FlowRowRow では、アイテムの幅の計算に重みが使用される方法が異なります。Rows の場合、重み付けは Row 内のすべての項目に基づいています。FlowRow の場合、重み付けは FlowRow コンテナ内のすべてのアイテムではなく、アイテムが配置されている行内のアイテムに基づいています。

たとえば、1 行に 4 つの項目があり、それぞれが異なる重み(1f, 2f, 1f3f)を持っている場合、合計重みは 7f です。行または列の残りのスペースは 7f で割られます。次に、各アイテムの幅は weight * (remainingSpace / totalWeight) を使用して計算されます。

Modifier.weight と最大アイテムを FlowRow または FlowColumn と組み合わせて、グリッド状のレイアウトを作成できます。このアプローチは、デバイスのサイズに合わせて調整されるレスポンシブ レイアウトを作成する場合に便利です。

重み付けを使用して実現できる効果には、いくつかの例があります。たとえば、次に示すように、アイテムのサイズが同じグリッドがあります。

フロー行で作成されたグリッド
図 3. FlowRow を使用してグリッドを作成する

アイテムのサイズが同じグリッドを作成するには、次の操作を行います。

val rows = 3
val columns = 3
FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = rows
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .weight(1f)
        .clip(RoundedCornerShape(8.dp))
        .background(MaterialColors.Blue200)
    repeat(rows * columns) {
        Spacer(modifier = itemModifier)
    }
}

重要な点は、別の項目を追加して 9 回ではなく 10 回繰り返すと、行全体の重みが 1f になるため、最後の項目が最後の列全体を占有することです。

グリッド上の最後のアイテムがフルサイズ
図 4. FlowRow を使用して、最後のアイテムが幅全体を占めるグリッドを作成する

重みは、Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio)Modifier.fillMaxWidth(fraction) などの他の Modifiers と組み合わせることができます。これらの修飾子はすべて連携して動作し、FlowRow(または FlowColumn)内のアイテムのレスポンシブ サイズ設定を可能にします。

アイテムのサイズが異なるグリッドを交互に配置することもできます。2 つのアイテムがそれぞれ幅の半分を占め、1 つのアイテムが次の列の幅全体を占めるようにします。

グリッドとフロー行を交互に配置
図 5. FlowRow(行のサイズが交互になっている)

これを実現するには、次のコードを使用します。

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 2
) {
    val itemModifier = Modifier
        .padding(4.dp)
        .height(80.dp)
        .clip(RoundedCornerShape(8.dp))
        .background(Color.Blue)
    repeat(6) { item ->
        // if the item is the third item, don't use weight modifier, but rather fillMaxWidth
        if ((item + 1) % 3 == 0) {
            Spacer(modifier = itemModifier.fillMaxWidth())
        } else {
            Spacer(modifier = itemModifier.weight(0.5f))
        }
    }
}

小数サイズ

Modifier.fillMaxWidth(fraction) を使用すると、アイテムが占有するコンテナのサイズを指定できます。これは、Row または Column に適用された場合の Modifier.fillMaxWidth(fraction) の動作とは異なります。Row/Column アイテムは、コンテナ全体の幅ではなく、残りの幅の割合を占有します。

たとえば、次のコードでは、FlowRowRow を使用すると結果が異なります。

FlowRow(
    modifier = Modifier.padding(4.dp),
    horizontalArrangement = Arrangement.spacedBy(4.dp),
    maxItemsInEachRow = 3
) {
    val itemModifier = Modifier
        .clip(RoundedCornerShape(8.dp))
    Box(
        modifier = itemModifier
            .height(200.dp)
            .width(60.dp)
            .background(Color.Red)
    )
    Box(
        modifier = itemModifier
            .height(200.dp)
            .fillMaxWidth(0.7f)
            .background(Color.Blue)
    )
    Box(
        modifier = itemModifier
            .height(200.dp)
            .weight(1f)
            .background(Color.Magenta)
    )
}

FlowRow: コンテナ全体の幅の 0.7 の割合の中央アイテム。

分数幅とフロー行

Row: 残りの Row 幅の 0.7% を占める中央のアイテム。

行を含む小数幅

fillMaxColumnWidth()fillMaxRowHeight()

FlowColumn または FlowRow 内のアイテムに Modifier.fillMaxColumnWidth() または Modifier.fillMaxRowHeight() を適用すると、同じ列または行内のアイテムは、その列または行内の最大のアイテムと同じ幅または高さを占有します。

たとえば、この例では FlowColumn を使用して Android デザートのリストを表示します。Modifier.fillMaxColumnWidth() がアイテムに適用されている場合と、適用されていない場合の各アイテムの幅の違いを確認できます。

FlowColumn(
    Modifier
        .padding(20.dp)
        .fillMaxHeight()
        .fillMaxWidth(),
    horizontalArrangement = Arrangement.spacedBy(8.dp),
    verticalArrangement = Arrangement.spacedBy(8.dp),
    maxItemsInEachColumn = 5,
) {
    repeat(listDesserts.size) {
        Box(
            Modifier
                .fillMaxColumnWidth()
                .border(1.dp, Color.DarkGray, RoundedCornerShape(8.dp))
                .padding(8.dp)
        ) {

            Text(
                text = listDesserts[it],
                fontSize = 18.sp,
                modifier = Modifier.padding(3.dp)
            )
        }
    }
}

各アイテムに適用される Modifier.fillMaxColumnWidth()

fillMaxColumnWidth

幅の変更が設定されていない(アイテムの折り返し)

列の最大幅の設定が空白