Compose의 Flow 레이아웃

FlowRowFlowColumnRowColumn와 유사한 컴포저블이지만 컨테이너의 공간이 부족할 때 항목이 다음 줄로 흘러간다는 점이 다릅니다. 이렇게 하면 여러 행 또는 열이 생성됩니다. 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.Start(Default개)

시작으로 정렬된 항목

Arrangement.SpaceBetween

사이에 공백이 있는 항목 정렬

Arrangement.Center

중앙에 정렬된 항목

Arrangement.End

마지막에 정렬된 항목

Arrangement.SpaceAround

항목 주위에 여백이 배치되어 있음

Arrangement.spacedBy(8.dp)

특정 dp만큼 간격을 둔 항목

FlowColumn의 경우 verticalArrangement에서 유사한 옵션을 사용할 수 있으며 기본값은 Arrangement.Top입니다.

교차축 정렬

교차 축은 기본 축과 반대 방향의 축입니다. 예를 들어 FlowRow에서는 세로축입니다. 컨테이너 내부의 전체 콘텐츠가 교차 축에 정렬되는 방식을 변경하려면 FlowRow의 경우 verticalArrangement를, FlowColumn의 경우 horizontalArrangement를 사용합니다.

FlowRow의 경우 다음 표는 항목에 다른 verticalArrangement를 설정하는 예를 보여줍니다.

FlowRow에 세로 정렬이 설정됨

결과

Arrangement.Top(Default개)

컨테이너 상단 정렬

Arrangement.Bottom

컨테이너 하단 배치

Arrangement.Center

컨테이너 중앙 배치

FlowColumn의 경우 horizontalArrangement에서 유사한 옵션을 사용할 수 있습니다. 교차 축의 기본 정렬은 Arrangement.Start입니다.

개별 항목 정렬

행 내에 개별 항목을 정렬하여 배치할 수 있습니다. 이는 현재 줄 내에서 항목을 정렬한다는 점에서 verticalArrangementhorizontalArrangement와 다릅니다. Modifier.align()로 이를 적용할 수 있습니다.

예를 들어 FlowRow에 있는 항목의 높이가 서로 다른 경우 행은 가장 큰 항목의 높이를 사용하여 Modifier.align(alignmentOption)을 항목에 적용합니다.

FlowRow에 세로 정렬이 설정됨

결과

Alignment.Top(Default개)

상단에 정렬된 항목

Alignment.Bottom

하단에 정렬된 항목

Alignment.CenterVertically

항목이 가운데 정렬됨

FlowColumn의 경우 유사한 옵션을 사용할 수 있습니다. 기본 정렬은 Alignment.Start입니다.

행 또는 열의 최대 항목 수

매개변수 maxItemsInEachRow 또는 maxItemsInEachColumn는 다음 줄로 래핑하기 전에 한 줄에서 허용할 최대 항목을 기본 축의 최대 항목을 정의합니다. 기본값은 Int.MAX_INT이며, 이 경우 크기가 선에 맞을 수 있는 한 최대한 많은 항목을 허용합니다.

예를 들어 maxItemsInEachRow를 설정하면 초기 레이아웃에 항목이 3개만 있도록 합니다.

설정된 최댓값 없음

maxItemsInEachRow = 3

플로우 행에 설정된 최댓값 없음 플로우 행에 설정된 최대 항목 수

지연 로드 흐름 항목

ContextualFlowRowContextualFlowColumnFlowRowFlowColumn의 특수 버전으로, 흐름 행 또는 열의 콘텐츠를 지연 로드할 수 있습니다. 또한 항목이 첫 번째 행에 있는지와 같이 항목 위치(색인, 행 번호, 사용 가능한 크기)에 대한 정보도 제공합니다. 이는 대량의 데이터 세트와 항목의 문맥 정보가 필요한 경우에 유용합니다.

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 컨테이너의 모든 항목이 아니라 항목이 배치된 줄의 항목을 기준으로 가중치가 적용됩니다.

예를 들어 한 줄에 모두 해당하는 항목 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를 사용하여 마지막 항목이 전체 너비를 차지하는 그리드 만들기

가중치를 다른 Modifiers(예: Modifier.width(exactDpAmount), Modifier.aspectRatio(aspectRatio) 또는 Modifier.fillMaxWidth(fraction))과 결합할 수 있습니다. 이러한 수정자는 모두 FlowRow (또는 FlowColumn) 내에서 항목의 반응형 크기를 조정할 수 있도록 함께 작동합니다.

또한 서로 다른 항목 크기의 교차 그리드를 만들 수도 있습니다. 이 경우 두 항목은 각각 너비의 절반을 차지하고 한 항목은 다음 열의 전체 너비를 차지합니다.

그리드와 흐름 행이 교차됨
그림 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)를 사용하면 항목이 차지해야 하는 컨테이너의 크기를 지정할 수 있습니다. 이는 Modifier.fillMaxWidth(fraction)Row 또는 Column에 적용될 때 작동하는 방식과 다릅니다. 즉, 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()

Modifier.fillMaxColumnWidth() 또는 Modifier.fillMaxRowHeight()FlowColumn 또는 FlowRow 내의 항목에 적용하면 같은 열이나 행에 있는 항목이 열/행에서 가장 큰 항목과 동일한 너비 또는 높이를 차지합니다.

예를 들어 이 예에서는 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

너비 변경이 설정되지 않음 (래핑 항목)

채우기 최대 열 너비가 설정되지 않음