
지난주에 태그 묶음 화면 새로 짜는데, 가로 스크롤로 뒤쪽 칩이 잘려나가더라고요. 결국 Compose 1.11 FlexBox API 로 줄바꿈 지점을 통째로 갈아엎었습니다.
Row + horizontalScroll 로 칩 12개 깔았더니 생긴 문제
QA 검수가 들어왔는데, 가로 스크롤 끝에 숨어 있는 필터 칩 4개를 사용자가 인지 못 한다는 리포트가 올라왔습니다. 화면 폭이 좁은 기기에서는 12개 칩 중 절반이 오른쪽 너머로 사라져 있는 상태였어요.
요구 조건은 단순했습니다. 화면 폭에 따라 자동 줄바꿈, 칩 간격 8dp, 줄 간격 12dp 유지. 말로 풀면 한 줄인데 코드로 옮기는 부분이 매번 발목을 잡았습니다.

FlowRow·SubcomposeLayout 으로 우회하던 한계
처음에는 FlowRow 를 썼어요. 줄바꿈은 잘 되는데 maxItemsInEachRow 와 horizontalArrangement 를 같이 걸어두면 마지막 줄 정렬이 미묘하게 틀어집니다. 너비 비례 분배 옵션도 없어서 "마지막 칩만 남는 공간을 채우게" 같은 디테일이 막혔습니다.
그다음엔 SubcomposeLayout 으로 줄바꿈을 직접 계산했어요. 한 줄에 들어갈지 다음 줄로 내릴지 매 자식마다 측정해서 누적 폭을 잡는 코드인데요, RTL 대응까지 깔면 70줄을 가뿐히 넘기게 됩니다. 47줄짜리 측정 로직을 한 번 짜두긴 했는데, 간격 값 하나 바꾸려고 들어갈 때마다 멘붕이 오더라고요.

Compose FlexBox API 로 자동 줄바꿈 3줄에 정리하기
androidx.compose.foundation:foundation-layout:1.11.0 에 합류한 FlexBox 는 CSS 의 display: flex 를 거의 그대로 옮겨온 컨테이너로 알려져 있어요. 2026년 4월 안정 버전부터 @ExperimentalFlexBoxApi Opt-in 으로 들어왔습니다.
저는 기존 측정 코드를 통째로 지우고 이렇게 줄였습니다.
@OptIn(ExperimentalFlexBoxApi::class)
@Composable
fun FilterChipGroup(tags: List<String>) {
FlexBox(
config = {
direction(FlexDirection.Row)
wrap(FlexWrap.Wrap)
gap(8.dp)
// 메인축/교차축 간격을 다르게 두려면
// rowGap(12.dp); columnGap(8.dp) 형태로 분리
},
) {
tags.forEach { tag ->
FilterChip(tag = tag)
}
}
}
wrap(FlexWrap.Wrap) 한 줄로 자동 줄바꿈이 들어가고, gap(8.dp) 한 줄로 칩·줄 간격이 깔립니다. 47줄짜리 측정 로직이 사라진 자리가 진짜 시원했어요.
특정 칩만 남는 공간을 다 채우게 하고 싶으면 자식에 Modifier.flex { grow(1f) } 를 붙이면 됩니다. CSS 의 flex: 1 1 auto 와 거의 같은 동작이라고 보시면 되겠습니다. 주축 정렬은 justifyContent(), 교차축은 alignItems(), 여러 줄일 때 줄 그룹 자체는 alignContent() — 이름이 길어서 처음엔 헷갈리는데 한 번 잡으면 빠르게 손에 붙어요.

FlowRow·LazyGrid 와 FlexBox 사용처 따로 잡기
그렇다고 기존 FlowRow 코드를 다 갈아엎으라는 얘기는 아닙니다. 구글 가이드가 새 코드는 FlexBox 쪽을 권장하는 방향이긴 한데, FlowRow 자체가 deprecated 된 건 아니에요. 잘 동작하는 화면을 마이그레이션 비용 들여 갈아엎을 이유는 없습니다.
제 기준은 이렇게 나눠두고 있어요.
- 칩·태그·토글 묶음처럼 아이템 20개 이하, 화면 폭에 따라 줄바꿈이 필요하면
FlexBox. - 이미
FlowRow로 안정적으로 도는 화면은 그대로 유지. - 아이템이 50개를 넘어갈 가능성이 있으면
LazyVerticalStaggeredGrid로 빠짐.
FlexBox 는 Lazy 미지원이라 첫 컴포지션에서 자식을 전부 측정합니다. 100개짜리 리스트를 무심코 넣으면 프레임 드롭이 생길 여지가 있는 편이에요. 개인적으로는 칩이 30개를 넘어갈 것 같은 구간엔 처음부터 Lazy 계열을 깔아두는 쪽을 추천합니다. 나중에 갈아엎는 비용보다 처음에 자리잡는 비용이 훨씬 싸거든요.
ExperimentalFlexBoxApi Opt-in, 도입 범위는 좁게
마지막으로 짚어둘 지점은 @ExperimentalFlexBoxApi Opt-in 표식입니다. 안정 버전에 들어왔지만 어노테이션이 붙어 있다는 건 시그니처가 바뀔 여지가 남아 있다는 뜻이에요. 공식 가이드에서 별도 측정 오버헤드 벤치마크 페이지는 아직 못 찾았습니다.
저는 디자인 시스템 라이브러리 안에 ChipGroup, TagCloud 같은 컴포저블 두 개만 FlexBox 로 감싸 두고, 화면 코드에서는 그 컴포저블만 불러 쓰는 쪽으로 도입 범위를 잡아뒀어요. 시그니처가 바뀌면 컴포저블 두 개만 손보면 되니까요.
47줄짜리 SubcomposeLayout 을 지운 그 금요일 저녁, PR 디프를 다시 열어보다가 한참 멍하니 들여다봤습니다. 깃 디프 왼쪽 빨간 영역에 측정 로직 47줄이 줄줄이 그어져 있고, 오른쪽 초록 영역에는 FlexBox 컨테이너 한 덩어리가 들어와 있던 그 화면. 그 자리에 그렇게 오래 머물러 있던 코드가, 알고 보니 컨테이너 한 줄로 대체될 자리였다는 걸 그제서야 받아들였습니다.

'Android 개발 > Jetpack Compose' 카테고리의 다른 글
| Navigation 3로 갈아타 봤습니다 — 화면 백스택을 상태로 직접 들고 있게 된 자리 (1) | 2026.07.06 |
|---|---|
| 안드로이드 스크린샷 테스트 3종 비교 — Roborazzi·Paparazzi·Compose Preview 갈라 써본 한 주 (0) | 2026.07.05 |
| Compose Composition tracing으로 Layout Inspector가 놓친 스크롤 잰크 잡기 (0) | 2026.06.28 |
| @PreviewWrapper 하나로 디자인 시스템 프리뷰 일관성 잡기 (0) | 2026.06.26 |
| LazyColumn 41번 재구성, Quail Layout Inspector로 진범 잡은 일주일 (0) | 2026.06.23 |