ConstraintLayout in Compose
ConstraintLayout은 컴포저블(Composable)들을 서로 상대적인 위치에 배치할 수 있게 해주는 레이아웃입니다. 여러 개의 중첩된 Row, Column, Box 또는 커스텀 레이아웃을 사용하는 대신으로 활용할 수 있습니다. 복잡한 정렬이 필요한 큰 레이아웃을 구현할 때 유용합니다.
다음과 같은 상황에서 ConstraintLayout 사용을 고려해보세요:
- 화면에 요소를 배치하기 위해 여러 개의 Column과 Row를 중첩 사용하는 것을 피하고, 코드의 가독성을 높이고자 할 때
- 컴포저블을 다른 컴포저블 기준으로 배치하거나, 가이드라인(guideline), 배리어(barrier), 체인(chain) 등을 기준으로 배치하고자 할 때
뷰 시스템(View system)에서는 중첩된 뷰보다 평평한 뷰 계층 구조(flat view hierarchy)가 성능상 더 유리했기 때문에, 복잡한 레이아웃을 만들 때 ConstraintLayout이 권장되었습니다.
하지만 Compose에서는 깊은 레이아웃 계층 구조도 효율적으로 처리할 수 있기 때문에, 이런 성능상의 이유는 Compose에서는 더 이상 고려사항이 아닙니다.
ConstraintLayout 시작하기
Compose에서 ConstraintLayout을 사용하려면, Compose 설정 외에 build.gradle 파일에 다음 의존성을 추가해야 합니다:
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"
참고:
constraintlayout-compose 아티팩트는 Jetpack Compose와 다른 버전 체계를 사용합니다. 최신 버전은 ConstraintLayout 릴리스 페이지에서 확인하세요.
Compose에서 ConstraintLayout은 다음과 같은 방식으로 동작합니다 (DSL 기반):
- ConstraintLayout 내에서 createRefs() 또는 createRefFor()를 사용하여 각 컴포저블에 대한 참조(reference) 를 생성합니다.
- constrainAs() modifier를 통해 제약 조건을 지정합니다. 이 modifier는 해당 컴포저블의 참조를 인자로 받아, 람다 블록 안에서 제약 조건을 설정할 수 있도록 합니다.
- 제약 조건은 linkTo() 등의 유틸리티 메서드를 사용하여 설정합니다.
- parent는 ConstraintLayout 자체를 나타내는 기본 제공 참조로, ConstraintLayout을 기준으로 위치를 지정할 때 사용할 수 있습니다.
아래는 ConstraintLayout을 사용하는 예시입니다:
@Composable
fun ConstraintLayoutContent() {
ConstraintLayout {
// 제약을 적용할 컴포저블에 대한 참조 생성
val (button, text) = createRefs()
Button(
onClick = { /* 어떤 작업 수행 */ },
// Button 컴포저블에 "button" 참조를 할당하고,
// ConstraintLayout의 상단에 배치되도록 제약 설정
modifier = Modifier.constrainAs(button) {
top.linkTo(parent.top, margin = 16.dp)
}
) {
Text("Button")
}
// Text 컴포저블에 "text" 참조를 할당하고,
// Button의 하단에 배치되도록 제약 설정
Text(
"Text",
Modifier.constrainAs(text) {
top.linkTo(button.bottom, margin = 16.dp)
}
)
}
}
이 코드는 다음과 같은 제약을 적용합니다:
- Button의 상단을 부모(ConstraintLayout)의 상단에 16.dp 마진을 두고 연결합니다.
- Text는 Button의 하단에 16.dp 마진을 두고 배치됩니다.
Decoupled API (제약 조건 분리 방식)
기존 ConstraintLayout 예제에서는 제약 조건을 각 컴포저블의 modifier 내부에 인라인으로 작성했습니다.
하지만 화면 구성에 따라 제약 조건을 바꾸거나, 두 개의 제약 조건 세트 간에 애니메이션을 적용하고자 하는 경우에는 제약 조건을 레이아웃에서 분리(Decoupled) 하는 것이 더 적합할 수 있습니다.
이러한 상황에서는 ConstraintLayout을 다음과 같은 방식으로 사용할 수 있습니다:
- ConstraintLayout에 ConstraintSet을 파라미터로 전달합니다.
- ConstraintSet에서 생성한 참조는 컴포저블에 layoutId modifier를 사용하여 연결합니다.
@Composable
fun DecoupledConstraintLayout() {
BoxWithConstraints {
val constraints = if (minWidth < 600.dp) {
decoupledConstraints(margin = 16.dp) // 세로 화면용 제약
} else {
decoupledConstraints(margin = 32.dp) // 가로 화면용 제약
}
ConstraintLayout(constraints) {
Button(
onClick = { /* 어떤 작업 수행 */ },
modifier = Modifier.layoutId("button")
) {
Text("Button")
}
Text("Text", Modifier.layoutId("text"))
}
}
}
private fun decoupledConstraints(margin: Dp): ConstraintSet {
return ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
constrain(button) {
top.linkTo(parent.top, margin = margin)
}
constrain(text) {
top.linkTo(button.bottom, margin)
}
}
}
이 예제는 화면 너비에 따라 서로 다른 마진 값을 갖는 제약 조건을 적용합니다.
- ConstraintSet은 제약 조건을 별도로 정의해 재사용 가능하게 만들며,
- layoutId는 해당 제약 조건과 컴포저블을 연결하는 역할을 합니다.
따라서 제약 조건을 변경해야 할 때는, 단순히 다른 ConstraintSet을 전달해주기만 하면 됩니다.
ConstraintLayout 개념
ConstraintLayout에는 Guideline, Barrier, Chain과 같은 개념이 포함되어 있으며, 이들은 컴포저블 내부에서 요소의 위치를 조정할 때 유용하게 사용됩니다.
Guidelines (가이드라인)
가이드라인은 레이아웃을 설계할 때 사용하는 시각적 기준선으로, 컴포저블을 이 가이드라인에 맞춰 배치할 수 있습니다.
가이드라인은 부모 컴포저블 내부에서 특정 dp 거리나 비율(%) 위치에 요소를 정렬할 때 유용합니다.
가이드라인에는 두 가지 종류가 있습니다:
- 수직 가이드라인: start, end
- 수평 가이드라인: top, bottom
ConstraintLayout {
// 부모 시작 지점에서 컴포저블 너비의 10% 지점에 수직 가이드라인 생성
val startGuideline = createGuidelineFromStart(0.1f)
// 부모 끝 지점에서 컴포저블 너비의 10% 지점에 수직 가이드라인 생성
val endGuideline = createGuidelineFromEnd(0.1f)
// 부모 위쪽에서 16dp 떨어진 위치에 수평 가이드라인 생성
val topGuideline = createGuidelineFromTop(16.dp)
// 부모 아래쪽에서 16dp 떨어진 위치에 수평 가이드라인 생성
val bottomGuideline = createGuidelineFromBottom(16.dp)
}
가이드라인을 만들기 위해서는 createGuidelineFrom 메서드를 사용하며, 생성된 가이드라인 참조는 Modifier.constrainAs() 블록 내에서 사용할 수 있습니다.
💡 참고: Row나 Column을 사용할 때는 Spacer 컴포저블을 활용해 비슷한 효과를 낼 수 있습니다.
Barriers (배리어)
배리어는 여러 컴포저블을 참조하여, 지정한 방향에서 가장 극단적인 위젯을 기준으로 가상의 가이드라인을 생성합니다.
배리어를 생성하려면 createTopBarrier() 를 사용하고, 그 외에도 createBottomBarrier(), createEndBarrier(), createStartBarrier() 를 사용할 수 있으며, 배리어를 구성할 참조들을 인자로 전달합니다.
ConstraintLayout {
val constraintSet = ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
val topBarrier = createTopBarrier(button, text)
}
}
생성된 배리어는 Modifier.constrainAs() 블록 안에서 사용할 수 있습니다.
참고: Row나 Column에서 유사한 효과를 얻고 싶다면 Intrinsic 측정 방식을 고려해 보세요.
Chains (체인)
체인은 하나의 축(수평 또는 수직)에서 컴포저블들을 그룹처럼 정렬할 수 있게 해줍니다.
다른 축은 별도로 제약을 설정할 수 있습니다.
체인을 생성하려면 createVerticalChain 또는 createHorizontalChain을 사용합니다:
ConstraintLayout {
val constraintSet = ConstraintSet {
val button = createRefFor("button")
val text = createRefFor("text")
val verticalChain = createVerticalChain(button, text, chainStyle = ChainStyle.Spread)
val horizontalChain = createHorizontalChain(button, text)
}
}
생성한 체인은 Modifier.constrainAs() 블록 안에서 제약 조건으로 사용할 수 있습니다.
체인은 다음과 같은 ChainStyle을 통해 여백을 다루는 방식을 설정할 수 있습니다:
- ChainStyle.Spread:
→ 첫 번째 컴포저블 앞과 마지막 컴포저블 뒤의 여백을 포함하여, 전체 공간을 균등하게 분산합니다. - ChainStyle.SpreadInside:
→ 양 끝의 여백 없이, 컴포저블들 사이에만 공간을 균등하게 분배합니다. - ChainStyle.Packed:
→ 컴포저블들을 서로 밀착시켜 중앙에 모은 뒤, 남은 공간은 양쪽 끝에 분산합니다.
참고: ConstraintLayout의 체인과 유사한 정렬 효과는 Row나 Column에서 다양한 Arrangement를 사용하여 구현할 수 있습니다.
끝.
참고자료
Compose의 ConstraintLayout | Jetpack Compose | Android Developers
이 페이지는 Cloud Translation API를 통해 번역되었습니다. Compose의 ConstraintLayout 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. ConstraintLayout은 화면에 다른 컴포
developer.android.com
https://kadosholy.tistory.com/132
[안드로이드] Virtual Helpers objects (Guideline, Barrier, Group, Placeholder)
안드로이드 - Virtual helpers objects (Guideline, Barrier, Group, Placeholder) 이전 글에서 ConstraintLayout을 사용할 때, 화면 구성을 위해 사용할 수 있는 제약 유형을 알아보고 그에 대한 속성과 사용방법에 대해
kadosholy.tistory.com
'Android > Compose' 카테고리의 다른 글
[Android] Compose의 IntrinsicSize.Min와 wrapContentHeight() 차이 (0) | 2025.07.12 |
---|---|
[Android] Compose 레이아웃에서 Intrinsic 측정 (0) | 2025.06.02 |
[Android] Compose의 정렬선(Alignment lines) (1) | 2025.06.02 |
[Android] Compose는 왜 View System과 달리 측정을 한번만 할까? (1) | 2025.06.01 |
[Android] Compose의 커스텀 레이아웃 (0) | 2025.06.01 |