
ConstraintLayout goneMargin은 View가 GONE 상태가 되었을 때 간격이 예상과 다르게 바뀌는 문제를 다룰 때 필요합니다. 일반 margin만 알면 visible 상태에서는 괜찮아 보이지만, 기준 View가 사라지는 순간 UI가 갑자기 붙어 보일 수 있습니다.
이번 글에서는 margin과 goneMargin을 visible/gone 상태 이미지로 비교합니다. 핵심은 margin은 일반 상태의 간격이고, goneMargin은 연결 대상이 GONE일 때의 대체 간격이라는 점입니다.

ConstraintLayout goneMargin을 이해하려면 margin부터 봐야 한다
ConstraintLayout에서 margin은 그냥 View 주변에 생기는 독립적인 공간이 아닙니다. 어떤 constraint에 연결되어 있을 때, 그 연결선 사이의 간격으로 작동합니다.
<TextView
android:id="@+id/titleText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:text="Title"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />위 예제에서 marginStart는 parent start와 titleText start 사이의 24dp 간격입니다. top margin도 parent top과 titleText top 사이의 간격입니다.

중요한 점은 margin이 단독으로 위치를 만들지 않는다는 것입니다. start 제약이 없다면 marginStart만으로는 가로 위치를 안정적으로 정할 수 없습니다. top 제약이 없다면 marginTop도 마찬가지입니다.
- constraint가 있어야 margin의 기준이 생긴다
- margin은 View 바깥쪽에 보이는 여백이다
- 같은 16dp라도 parent 기준인지 sibling 기준인지에 따라 화면에서 다르게 느껴진다
sibling 사이 margin은 두 View 사이 간격으로 보인다
B를 A 오른쪽에 붙이고 B에 marginStart를 주면, 그 margin은 A와 B 사이의 간격으로 보입니다. 이때 margin은 B가 가진 속성이지만, 실제로는 A와 B 사이 거리로 읽힙니다.
<TextView
android:id="@+id/labelText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Label"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/valueText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Value"
app:layout_constraintStart_toEndOf="@id/labelText"
app:layout_constraintTop_toTopOf="@id/labelText" />
실무에서는 label과 value 사이에 margin을 자주 둡니다. 이때 label 길이가 고정되어 있으면 margin만으로 충분합니다. 하지만 label 길이가 언어별로 크게 달라진다면 Barrier를 고려해야 합니다.
<TextView
android:id="@+id/valueText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_constraintStart_toEndOf="@id/labelText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/labelText" />valueText의 width를 0dp로 두고 end를 parent에 연결하면, label 뒤쪽부터 parent 끝까지 남은 폭을 사용할 수 있습니다. 이 내용은 4~5편에서 더 자세히 다룹니다.
View.GONE이 되면 기준이 달라질 수 있다
문제는 labelText가 GONE이 되는 순간입니다. valueText는 labelText의 end에 연결되어 있었지만, 기준 View가 사라지면 간격이 예상과 다르게 보일 수 있습니다. ConstraintLayout 레퍼런스는 이런 상황을 위해 goneMargin 속성을 제공합니다.
<TextView
android:id="@+id/valueText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
app:layout_goneMarginStart="48dp"
android:text="Value"
app:layout_constraintStart_toEndOf="@id/labelText"
app:layout_constraintTop_toTopOf="@id/labelText" />일반 상태에서는 marginStart 16dp가 적용됩니다. labelText가 GONE이면 goneMarginStart 48dp를 대체 간격으로 사용할 수 있습니다.

정확히는 labelText가 GONE일 때 valueText가 parent를 새 기준으로 삼는다고 단순화하면 오해가 생깁니다. 연결 대상은 여전히 labelText입니다. 다만 GONE 대상은 크기가 0처럼 취급되고, 이때 일반 margin 대신 goneMargin이 적용됩니다.
INVISIBLE과 GONE은 다르게 봐야 한다
INVISIBLE은 View가 보이지 않을 뿐 레이아웃 공간은 유지합니다. 반면 GONE은 레이아웃 계산에서 공간이 사라진 것처럼 다뤄집니다. 그래서 goneMargin은 INVISIBLE이 아니라 GONE 상태를 다룰 때 의미가 있습니다.

- INVISIBLE: 보이지 않지만 자리 유지
- GONE: 보이지 않고 자리도 사라진 것처럼 계산
- goneMargin: 연결 대상이 GONE일 때 사용할 대체 margin
goneMargin을 써야 하는 상황
- 기준 View가 조건에 따라 GONE이 되는 화면
- 아이콘이 있을 때와 없을 때 텍스트 시작 위치가 달라야 하는 UI
- 설명 문구가 사라져도 버튼이 parent에 너무 붙지 않아야 하는 UI
- 로딩/에러/빈 상태 전환에서 간격을 유지해야 하는 UI
반대로 기준 View가 절대 GONE이 되지 않는다면 goneMargin은 필요하지 않습니다. 모든 margin을 goneMargin으로 복사해 넣는 방식은 유지보수만 어렵게 만듭니다.
좋은 기준은 “이 View가 사라질 수 있는가”입니다. 사라질 수 있다면 연결된 View의 margin이 어떻게 보일지 미리 확인합니다. 사라질 수 없다면 일반 margin만으로 충분합니다.
자주 하는 실수
- constraint가 없는데 margin만 넣고 위치가 바뀌길 기대한다
- View.GONE 상태를 고려하지 않고 일반 margin만 넣는다
- goneMargin을 기준 View가 아니라 움직이는 View 쪽에 넣어야 한다는 점을 놓친다
- INVISIBLE과 GONE을 같은 상태처럼 취급한다
View가 안 보이거나 0dp와 margin이 함께 깨지는 문제는 ConstraintLayout 디버깅 가이드에서도 따로 정리했습니다. 이 글에서는 그중 간격과 GONE 상태에만 집중합니다.
정리
ConstraintLayout goneMargin은 조건부 UI에서 간격이 깨지는 문제를 줄여주는 속성입니다. 일반 margin은 평소의 간격이고, goneMargin은 연결 대상이 GONE일 때의 대체 간격입니다. 두 값을 같은 값으로 둘 수도 있지만, 의도는 분리해서 생각하는 편이 좋습니다.
- margin은 일반 상태의 간격
- goneMargin은 연결 대상이 GONE일 때의 대체 간격
- INVISIBLE은 자리를 유지하므로 goneMargin 상황과 다름
- 조건부 UI에서는 기준 View가 사라지는지 먼저 확인
아래에는 이 편보다 앞에서 다룬 글만 모았습니다. 아직 뒤 편은 발행 전이므로 링크하지 않습니다.
공식 기준은 AndroidX ConstraintLayout 레퍼런스와 Android View visibility 레퍼런스를 확인했습니다.