
ConstraintLayout 오류는 값을 조금씩 바꿔 보면서 맞히는 문제가 아닙니다. View가 안 보이거나 0dp가 깨질 때는 위치, 크기, visibility, helper 참조를 순서대로 확인해야 원인이 빨리 보입니다.
이번 글에서는 ConstraintLayout에서 자주 만나는 문제를 증상 → 원인 → 수정 기준으로 정리합니다. 앞에서 다룬 0dp, bias, chain, goneMargin, baseline, percent, Group, Barrier 개념을 디버깅 관점으로 다시 연결하겠습니다.
ConstraintLayout 오류 디버깅 순서

- View가 아예 이상한 위치에 있으면 가로/세로 constraint부터 확인한다.
- 0dp가 기대한 크기로 안 나오면 해당 축의 양쪽 제약을 확인한다.
- bias가 안 먹으면 양쪽 제약이 실제로 있는지 본다.
- chain이 깨지면 View 사이 연결과 양끝 parent 연결을 함께 본다.
- GONE 전환 뒤 간격이 이상하면 margin과 goneMargin을 나눠 본다.
- Group, Barrier, Flow가 안 먹으면 constraint_referenced_ids 오타와 누락부터 확인한다.
중요한 점은 순서입니다. 화면이 틀어졌다고 바로 margin 값을 바꾸면 원인을 가릴 수 있습니다. 먼저 제약 구조가 맞는지 보고, 그 다음 크기와 helper를 보는 편이 안전합니다.
증상별로 먼저 볼 위치
- View가 왼쪽 위에 붙어 보인다: 수평/수직 constraint 누락을 먼저 본다.
- View가 너무 넓거나 너무 좁다: width, height, 0dp, min/max 값을 같이 본다.
- 가운데 정렬이나 bias가 안 된다: 양쪽 제약이 모두 있는지 본다.
- 여러 버튼 간격이 균등하지 않다: chain 연결, chainStyle 위치, weight 조건을 본다.
- 텍스트가 길 때만 겹친다: Barrier, 0dp TextView, maxLines, 화면 폭을 함께 본다.
- 특정 상태에서만 깨진다: visibility와 goneMargin, Group 참조 id를 본다.
이렇게 증상과 확인 위치를 먼저 연결해 두면 수정 방향이 좁아집니다. 예를 들어 텍스트가 길 때만 겹친다면 margin을 키우기 전에 TextView의 width가 0dp인지, end 제약이 있는지, Barrier가 필요한 구조인지 먼저 확인해야 합니다.
디버깅 전에 재현 범위를 고정한다
같은 XML도 화면 폭, 텍스트 길이, visibility 상태에 따라 다르게 보일 수 있습니다. 그래서 ConstraintLayout 오류를 볼 때는 먼저 어떤 조건에서 깨지는지 고정해야 합니다.

- 작은 화면과 큰 화면 중 어디에서 깨지는가.
- 짧은 텍스트와 긴 텍스트 중 어느 쪽에서 깨지는가.
- VISIBLE, INVISIBLE, GONE 중 어떤 상태에서 깨지는가.
- Preview만 깨지는지, 실제 기기나 에뮬레이터에서도 깨지는가.
이 과정을 빼면 XML 수정이 맞아서 해결된 것인지, 우연히 다른 화면 폭에서만 정상처럼 보인 것인지 구분하기 어렵습니다.
View가 안 보일 때 먼저 볼 것
ConstraintLayout에서 View가 안 보인다고 해서 항상 visibility 문제는 아닙니다. 제약이 부족해서 예상 밖 위치에 놓였거나, 0dp 크기 계산이 실패해서 거의 보이지 않는 것처럼 느껴질 수 있습니다.

<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title" />위 코드는 TextView 자체는 존재하지만 위치 기준이 없습니다. ConstraintLayout에서는 View가 어느 기준에 붙는지 알 수 있어야 합니다. 그래서 최소한 한 축의 가로 기준과 한 축의 세로 기준을 먼저 갖춰야 합니다.
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />이 정도만으로도 디버깅 출발점이 달라집니다. 이제 View가 어디에서 시작해야 하는지 분명해졌고, 다음 문제는 크기나 다른 View와의 관계로 좁혀집니다.
0dp가 깨지는 가장 흔한 이유
0dp는 크기를 0으로 만들겠다는 뜻이 아닙니다. ConstraintLayout에서는 match constraints로 읽히며, 제약 사이의 남은 공간을 기준으로 크기를 계산하겠다는 뜻에 가깝습니다.

따라서 가로 방향에서 0dp를 썼다면 start와 end 기준이 모두 있어야 의도가 분명합니다. 한쪽 제약만 있으면 어떤 범위 사이를 채워야 하는지 판단하기 어렵습니다.
<!-- 문제: width는 0dp인데 end 제약이 없다 -->
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /><!-- 수정: start와 end 사이에서 폭을 계산한다 -->
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />세로 방향도 같습니다. height를 0dp로 쓴다면 top과 bottom 기준이 함께 있어야 합니다. 4편과 5편에서 다룬 match_constraint 규칙은 오류 디버깅에서도 가장 먼저 확인할 항목입니다.
bias가 안 먹는 이유
bias는 View를 무조건 이동시키는 속성이 아닙니다. start와 end, 또는 top과 bottom처럼 양쪽 제약이 잡힌 상태에서 그 사이 위치를 조절하는 값입니다.

<!-- 문제: start만 있으므로 horizontalBias가 의미를 갖기 어렵다 -->
<Button
android:id="@+id/actionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0.8"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /><!-- 수정: start/end 사이에서 bias를 적용한다 -->
<Button
android:id="@+id/actionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0.8"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />verticalBias도 같은 방식으로 봅니다. top과 bottom이 모두 있어야 위쪽에 가까운지, 아래쪽에 가까운지를 조절할 수 있습니다.
chain이 깨졌을 때 보는 기준
chain은 여러 View를 서로 연결해서 하나의 배치 그룹처럼 다루는 기능입니다. 그런데 View끼리만 연결되어 있고 양끝 기준이 빠져 있으면, 기대한 spread, spread_inside, packed 결과가 나오기 어렵습니다.

- 첫 View의 start가 parent 또는 기준 View에 연결되어 있는가.
- 마지막 View의 end가 parent 또는 기준 View에 연결되어 있는가.
- 각 View가 앞뒤 View와 서로 연결되어 있는가.
- chainStyle을 어느 View에 넣었는가. 보통 chain head 기준으로 읽는다.
- weight를 쓰는 View의 width 또는 height가 0dp인지 확인한다.
chain 오류는 XML 한 줄만 보면 놓치기 쉽습니다. A, B, C 세 View가 있다면 A의 start, C의 end, 그리고 A-B-C 사이의 양방향 연결을 함께 봐야 합니다.
<!-- 문제: A, B, C는 이어져 있지만 parent 양끝 기준이 부족하다 -->
<Button
android:id="@+id/a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toStartOf="@id/b" />
<Button
android:id="@+id/b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/a"
app:layout_constraintEnd_toStartOf="@id/c" />
<Button
android:id="@+id/c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/b" /><!-- 수정: 첫 View와 마지막 View가 parent 기준까지 이어진다 -->
<Button
android:id="@+id/a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/b" />
<Button
android:id="@+id/b"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/a"
app:layout_constraintEnd_toStartOf="@id/c" />
<Button
android:id="@+id/c"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/b"
app:layout_constraintEnd_toEndOf="parent" />실제 XML에서는 top 또는 baseline 같은 세로 제약도 함께 필요합니다. 여기서는 chain의 가로 연결만 보이기 위해 관련 속성만 줄였습니다.
GONE 이후 간격이 이상할 때
View.GONE은 단순히 안 보이는 상태가 아닙니다. ConstraintLayout에서는 GONE View 주변의 간격 계산이 달라질 수 있습니다. 이때 기본 margin만 보고 있으면 왜 간격이 줄거나 붙는지 헷갈립니다.

<Button
android:id="@+id/actionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_goneMarginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/messageView"
app:layout_constraintStart_toStartOf="parent" />goneMargin은 연결 대상이 GONE이 되었을 때 적용할 간격을 따로 정하는 장치입니다. 따라서 상태별 화면이나 옵션 영역처럼 특정 View가 자주 사라지는 UI에서는 margin과 goneMargin을 함께 점검해야 합니다.
baseline, percent, ratio 오류는 전제부터 본다
baseline 정렬은 TextView처럼 baseline을 가진 View끼리 의미가 있습니다. 일반 View에 글자 기준선을 기대하면 의도와 다르게 느껴질 수 있습니다.
percent 크기는 0dp와 함께 읽어야 하는 경우가 많습니다. 예를 들어 percent width를 기대한다면 width가 0dp인지, start/end 기준이 있는지 같이 확인해야 합니다.
dimensionRatio도 마찬가지입니다. 한 축이 제약으로 계산될 수 있어야 비율이 안정적으로 적용됩니다. 이미지 비율이 안 맞는다면 ratio 문자열보다 width, height, 제약 조건을 먼저 보세요.
helper가 안 먹을 때는 참조 id를 본다
Group, Barrier, Flow 같은 helper는 참조한 View를 대상으로 동작합니다. 그래서 helper 관련 오류는 속성 설명보다 constraint_referenced_ids 목록을 먼저 확인하는 편이 빠릅니다.

<androidx.constraintlayout.widget.Group
android:id="@+id/errorGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="errorTitle,errorMessage,retryButton" />여기서 id 하나가 빠지면 그 View만 같이 숨겨지지 않습니다. Barrier도 마찬가지입니다. 긴 텍스트를 기준으로 버튼을 아래에 붙이고 싶다면, Barrier가 실제로 어떤 TextView들을 참조하는지 확인해야 합니다.
<androidx.constraintlayout.widget.Barrier
android:id="@+id/messageBottomBarrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="bottom"
app:constraint_referenced_ids="errorTitle,errorMessage" />Group은 배치 컨테이너가 아니고, Barrier는 화면에 보이는 선이 아닙니다. helper는 기준을 보조하거나 여러 View를 함께 다루는 도구이므로, 각 View 자체의 제약은 여전히 따로 맞아 있어야 합니다.
Layout Inspector와 Preview를 같이 본다
Android Studio Preview만 보면 특정 화면 폭에서는 정상인데 실제 기기에서는 깨지는 경우가 있습니다. 특히 0dp, percent, Flow, 긴 텍스트, 다국어 문장은 화면 폭에 따라 결과가 달라질 수 있습니다.
- Preview에서 작은 폭과 큰 폭을 모두 확인한다.
- 실제 기기 또는 에뮬레이터에서 긴 텍스트 상태를 넣어 본다.
- Layout Inspector로 실제 visibility, width, height, constraint 결과를 확인한다.
- 문제가 있는 View 하나만 보지 말고 기준이 되는 sibling View도 같이 본다.
Preview에서만 쓰는 tools: 속성은 디버깅 보조용으로 유용하지만 런타임 동작을 바꾸지는 않습니다. Preview에서 보이게 만든 설정이 실제 기기에서도 적용된다고 착각하지 않도록, 마지막에는 반드시 런타임 상태를 확인해야 합니다.
디버깅할 때 피해야 할 방식
- margin 값을 계속 바꿔서 우연히 맞추기
- 0dp 문제를 무조건 wrap_content로 바꿔 덮기
- bias가 안 먹는 이유를 보지 않고 좌표처럼 쓰기
- chain이 깨졌는데 chainStyle 값만 바꾸기
- helper 참조 id를 확인하지 않고 Group이나 Barrier 자체를 의심하기
이런 방식은 당장 화면이 맞아 보일 수는 있지만 다음 상태나 다른 기기 폭에서 다시 깨질 가능성이 큽니다. ConstraintLayout은 관계를 맞추는 레이아웃이기 때문에, 수정도 관계 단위로 해야 합니다.
한 번에 정리
- ConstraintLayout 오류는 위치, 크기, visibility, helper 순서로 본다.
- View가 이상한 위치에 있으면 가로/세로 제약이 모두 있는지 먼저 확인한다.
- 0dp 오류는 해당 축의 양쪽 제약 누락에서 자주 시작된다.
- bias는 양쪽 제약 사이에서만 자연스럽게 동작한다.
- chain은 View 사이 연결과 양끝 기준, chain head를 함께 봐야 한다.
- GONE 상태 전환이 있으면 goneMargin과 helper 참조 id까지 확인한다.
ConstraintLayout 오류 디버깅의 핵심은 값을 바꾸는 것이 아니라, 현재 View가 어떤 기준과 관계로 계산되는지 다시 읽는 것입니다.
마무리
이번 편은 시리즈에서 다룬 주요 속성을 문제 해결 관점으로 다시 묶은 글입니다. 0dp, bias, chain, goneMargin, Barrier, Group은 각각 따로 외울 때보다 실제 오류를 잡을 때 더 선명하게 이해됩니다.
공식 기준은 Android Developers ConstraintLayout 가이드, AndroidX ConstraintLayout API reference, AndroidX Group API reference, AndroidX Barrier API reference를 확인했습니다. 다음 20편에서는 프로필 카드, 상품 카드, 채팅 입력창 같은 실전 예제로 시리즈를 마무리하겠습니다.
Android ConstraintLayout 완전 정리 이전 글 모음
Android ConstraintLayout 완전 정리 (1) – ConstraintLayout 기본 사용법: LinearLayout과 다른 제약 기반 배치 이해하기
Android ConstraintLayout 완전 정리 (3) – ConstraintLayout goneMargin과 margin 차이: View.GONE일 때 간격이 바뀌는 이유
Android ConstraintLayout 완전 정리 (5) – ConstraintLayout match_constraint 완전 정리: 0dp가 남은 공간을 채우는 원리
Android ConstraintLayout 완전 정리 (7) – ConstraintLayout baseline 정렬: TextView 글자 기준선을 맞추는 방법
Android ConstraintLayout 완전 정리 (8) – ConstraintLayout dimensionRatio 사용법: 1:1, 16:9 이미지 비율 맞추기
Android ConstraintLayout 완전 정리 (9) – ConstraintLayout percent min max 크기 정리: 화면 크기에 맞게 View 조절하기
Android ConstraintLayout 완전 정리 (10) – ConstraintLayout chain 사용법: spread, spread_inside, packed 차이
Android ConstraintLayout 완전 정리 (11) – ConstraintLayout chain weight와 bias: 같은 폭, 다른 비율 배치 만들기
Android ConstraintLayout 완전 정리 (12) – ConstraintLayout Guideline 사용법: percent 기준선으로 화면 나누기
Android ConstraintLayout 완전 정리 (13) – ConstraintLayout Barrier 사용법: 텍스트 길이에 따라 동적 기준선 만들기
Android ConstraintLayout 완전 정리 (14) – ConstraintLayout Group과 Placeholder: 여러 View 숨기기와 위치 바꾸기
Android ConstraintLayout 완전 정리 (15) – ConstraintLayout Flow 사용법: Chip과 태그를 자동 줄바꿈 배치하기
Android ConstraintLayout 완전 정리 (16) – ConstraintLayout Layer 사용법: 여러 View를 함께 이동, 회전, 확대하기
Android ConstraintLayout 완전 정리 (17) – ConstraintSet 사용법: 코드로 ConstraintLayout 제약 바꾸기
Android ConstraintLayout 완전 정리 (18) – ConstraintLayout 상태별 레이아웃 설계: loading, empty, error 화면 만들기