Compose로 UI를 작성하다 보면, 분명히 똑같은 modifier들을 썼는데 화면에 그려지는 결과가 다르게 나오는 경우를 종종 마주친다. 필자도 처음 Compose를 접했을 때 padding과 background 순서를 바꿔가며 한참 헤맨 기억이 있는데, 최근 다시 이 주제를 정리할 기회가 생겨 자주 헷갈리는 네 가지 조합을 직접 비교해보며 정리해보고자 한다.
Modifier는 순서대로 적용된다
Compose의 Modifier 체인은 왼쪽(위)에서 오른쪽(아래) 순서로 그대로 적용된다. 문제는 같은 modifier들을 어떤 순서로 나열하느냐에 따라 layout(크기·위치), drawing(그리는 영역), 입력 처리(클릭 가능 영역) 결과가 전부 달라질 수 있다는 점이다. 말로만 들으면 당연해 보이는데, 막상 코드를 짜다 보면 이 순서를 놓쳐서 의도와 다른 결과를 보는 일이 생각보다 잦다.
그래서 네 가지 대표적인 조합을 좌우로 나란히 배치해서 직접 비교할 수 있는 예제를 만들었다.
1. padding ↔ background
padding이 먼저 오면 그 안쪽의 "줄어든" 영역에만 배경이 칠해지고, background가 먼저 오면 전체 영역에 배경이 칠해진 뒤 안쪽에 padding이 생긴다.
// padding → background : 배경이 안쪽으로 들어가 작아진다
Box(
modifier = Modifier
.size(120.dp)
.padding(16.dp)
.background(Color.Blue)
)
// background → padding : 배경은 그대로, 콘텐츠만 안쪽으로 들어간다
Box(
modifier = Modifier
.size(120.dp)
.background(Color.Blue)
.padding(16.dp)
)
카드 배경색이 은은하게 깔리길 원했는데 padding을 먼저 적용해서 배경이 예상보다 작게 나온 경험이 있다면, 바로 이 순서 문제였을 가능성이 크다.
2. border ↔ clip
clip이 먼저 오면 모서리가 잘리면서 그 위에 그려질 border까지 함께 잘려나가고, border가 먼저 오면 테두리는 사각형 그대로 그려진 채 내용만 둥글게 잘린다.
// clip → border : 원형으로 잘리고, border도 원형으로 그려진다
Box(
modifier = Modifier
.clip(CircleShape)
.border(4.dp, Color.Red, CircleShape)
)
// border → clip : border는 사각형 그대로, 안쪽 콘텐츠만 원형으로 잘린다
Box(
modifier = Modifier
.border(4.dp, Color.Red)
.clip(CircleShape)
)
둥근 프로필 이미지에 테두리를 둘렀는데 모서리 부분 테두리가 뭉개져 보인다면, border를 clip보다 먼저 적용했을 확률이 높다.
3. clickable ↔ padding
여기서부터는 그리는 영역이 아니라 클릭 가능한 영역이 문제가 된다. clickable이 먼저 오면 그 시점의(아직 padding이 적용되지 않은) 영역만 hit-test 대상이 되고, padding이 먼저 오면 padding까지 포함한 더 넓은 영역이 클릭 대상이 된다.
// clickable → padding : padding 영역은 클릭되지 않는다 (클릭 영역이 좁음)
Box(
modifier = Modifier
.background(Color.Yellow)
.clickable { onClick() }
.padding(20.dp)
.background(Color.Orange)
)
// padding → clickable : padding까지 포함해서 클릭된다 (클릭 영역이 넓음)
Box(
modifier = Modifier
.background(Color.Yellow)
.padding(20.dp)
.background(Color.Orange)
.clickable { onClick() }
)
버튼처럼 보이는 영역인데 가장자리를 눌렀을 때 반응이 없다는 버그 리포트를 받아본 적이 있다면, clickable이 padding보다 먼저 걸려 있어서 그럴 가능성이 크다. 실제로 예제에서 좌우 박스를 각각 눌러보면 클릭 카운트가 늘어나는 범위가 눈에 띄게 다르다.
4. size ↔ padding
크기 자체도 순서에 영향을 받는다. size가 먼저 오면 지정한 크기 안에서 padding이 적용되어 콘텐츠 영역이 size -
padding*2만큼 줄어들고, padding이 먼저 오면 그 위에 size가 더해져서 바깥 크기 자체가 size + padding*2로 커진다.
// size → padding : 바깥 크기는 120dp로 고정
Box(
modifier = Modifier
.size(120.dp)
.padding(16.dp)
)
// padding → size : 바깥 크기가 120 + 16*2 = 152dp로 커진다
Box(
modifier = Modifier
.padding(16.dp)
.size(120.dp)
)
디자인 스펙대로 120dp를 지정했는데 실제 레이아웃에서 더 크게 잡히는 걸 발견했다면, padding을 size보다 먼저 넣어서 그럴 수 있다. 이 케이스는 다른 세 개보다 훨씬 눈치채기 어려운 편이라 직접 겪어보기 전엔 잘 모르고 지나가기 쉽다.
정리
네 가지를 나란히 비교해보니 규칙 자체는 단순했다. Modifier 체인은 위에서 아래로(왼쪽에서 오른쪽으로) 순서대로 적용되고, padding·size·offset처럼 layout phase에 영향을 주는 modifier는 자식에게 전달되는 제약을 바꾸고, background·border·clip처럼 drawing phase에 영향을 주는 modifier는 적용되는 시점의 영역에 그려진다. clickable의 위치는 그 시점의 hit-test 영역을 그대로 결정한다.
결국 원하는 결과가 안 나올 때는 이론을 다시 떠올리기보다, 의심 가는 두 줄의 순서를 바꿔서 직접 비교해보는 게 제일 빠른 것 같다. 이번 예제도 그런 식으로 좌우 비교 화면을 만들어두니 어떤 게 헷갈리는 조합인지 한눈에 들어와서 유용했다.
이번에는 Compose Modifier 순서가 결과에 미치는 영향에 대하여 작성해 보았다. 비슷하게 순서가 중요한 다른 modifier 조합들도 있을 것 같으니, 다음에 발견하면 이 글에 이어서 정리해볼 생각이다.
해당 게시글에 사용한 예제는 다음 Github에 올려두었다.
https://github.com/HeeGyeong/ComposeSample
GitHub - HeeGyeong/ComposeSample: This project provides various examples needed to actually use Jetpack Compose.
This project provides various examples needed to actually use Jetpack Compose. - HeeGyeong/ComposeSample
github.com