본문 바로가기
Android/Compose

[Android] Compose의 ConstraintLayout

by 태크민 2025. 6. 5.

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를 사용하여 구현할 수 있습니다.

 

 

 

끝.


참고자료

https://developer.android.com/develop/ui/compose/layouts/constraintlayout?_gl=1*jg1zm3*_up*MQ..*_ga*OTkzNjI4NTA4LjE3NDg4MjkyNDA.*_ga_6HH9YJMN9M*czE3NDg4MjkyNDAkbzEkZzAkdDE3NDg4MzAxODIkajYwJGwwJGgxMjgxMjk2MDkz#barriers

 

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