Modifier — Compose에서의 수정자 체인과 성능

저자: IT Sectr 게시일: 2026-06-28 읽는 시간: 8 분

Modifier는 Jetpack Compose의 불변 객체로, UI 구성 요소의 속성(크기, 패딩, 배경, 제스처 처리 및 동작)을 정의합니다. 수정자는 순차적 호출을 통해 체인으로 결합되며, 적용 순서가 결과에 결정적인 영향을 미칩니다. Google Android Developers, 2026에 따르면 Modifier의 올바른 사용은 선언형 UI에서 유연하고 성능 좋은 인터페이스를 구축하는 기초입니다.

핵심 사항

  • Modifier는 UI 구성 요소의 모양과 동작을 설명하는 불변 객체
  • 체인은 순차적으로 구축되며, 순서가 표시에 영향을 미침
  • 순서가 중요: padding → size와 size → padding은 다름
  • Modifier.composed로 사용자 정의 복합 수정자 생성 가능
  • 최적화: 매 재구성마다 Modifier를 다시 생성하지 않도록 주의

Jetpack Compose에서 Modifier란

Modifier는 androidx.compose.ui 패키지의 인터페이스로 Composite 패턴을 구현합니다. 각 수정자는 체인 요소로, 이전 요소를 감싸고 자신의 동작을 추가합니다. Modifier는 불변입니다. 변경 사항이 있으면 체인에 새 요소를 추가하여 복사본으로 새 객체가 생성됩니다. 이를 통해 단일 Modifier를 여러 구성 요소 간에 안전하게 공유할 수 있습니다.

기본 수정자 함수는 동반 객체 Modifier를 통해 호출됩니다(예: Modifier.padding(), Modifier.fillMaxWidth()). 각 함수는 추가된 요소와 함께 새 Modifier를 반환합니다. 여러 수정자가 있는 경우 체인으로 결합됩니다: Modifier.padding(16.dp).fillMaxWidth().background(Color.Blue). 순서는 UI 요소 기준으로 외부에서 내부로 진행됩니다.

기존 View에서는 setter를 통해 속성이 설정되었지만(view.setPadding(...), view.setBackground(...)), Compose에서 Modifier는 선언적 설명입니다. 구성 요소는 런타임에 수정자를 “적용”하지 않습니다. LayoutNode가 컴포지션 중에 Modifier 체인을 탐색하고 Modifier.Element 목록을 구축하여 측정 및 레이아웃 단계에서 처리합니다.

수정자 체인과 적용 순서

수정자의 순서는 Compose에서 가장 흔한 실수 중 하나입니다. 각 수정자는 이전 것을 감싸고, 작업은 외부에서 내부로 적용됩니다. 예: padding(16.dp).clickable { }: 먼저 요소 주위에 패딩이 추가되고, 클릭 영역에 패딩이 포함됩니다. clickable { }.padding(16.dp): 클릭 영역이 먼저 요소 크기와 같고, 그 주위에 패딩이 추가됩니다 — 패딩 클릭은 작동하지 않습니다.

기억 규칙: 체인을 왼쪽에서 오른쪽으로 읽고 외부에서 내부로 적용하세요. 첫 번째 수정자는 가장 바깥쪽이며 요소 주변 영역에 적용됩니다. 마지막은 가장 안쪽이며 콘텐츠에 직접 적용됩니다. 크기 수정자(size, fillMaxWidth)는 부모로부터 패딩이 필요한 경우 패딩 뒤에, 콘텐츠를 먼저 제약한 다음 중앙에 배치해야 하는 경우 패딩 앞에 와야 합니다.

예: size(100.dp).padding(10.dp) — 고정 크기 100dp 요소, 외부에 10dp 패딩(최종 크기 120dp). padding(10.dp).size(100.dp) — 10dp 패딩이 사용 가능 공간을 (부모 - 20dp)로 줄이고, size(100dp)가 부모를 초과할 수 있습니다. 표시 테스트를 사용하여 결과를 확인하면서 항상 순서를 신중하게 고려하세요.

순서결과
padding → clickable패딩 영역에서도 클릭 작동
clickable → padding콘텐츠에서만 클릭 작동, 패딩은 데드 존
size → padding요소 size(100), 외부 패딩 → 100+2*pad
padding → size패딩이 공간을 줄이고 size가 경계 초과 가능
background → padding배경이 외부 영역을 포함한 전체 요소를 채움
padding → background배경이 패딩 내부만 (외부 영역은 투명)

수정자 유형: 크기, 패딩, 장식 및 동작

표준 Compose 라이브러리에는 약 50개 이상의 수정자가 카테고리로 나뉘어 있습니다. 크기 및 위치 지정: Modifier.size(), width(), height(), fillMaxSize(), fillMaxWidth(), fillMaxHeight(), defaultMinSize(), requiredSize(). 패딩 및 테두리: padding(), offset(), margin(부모 패딩 또는 Layout을 통해 설정). 장식: background(), border(), clip(), alpha(), shadow(), blur().

동작 및 제스처: clickable(), combinedClickable(), pointerInput(), draggable(), swipeable(). 컨테이너 내 레이아웃: weight()(Row/Column용), align(), alignBy(), matchParentSize(). 의미론 및 접근성: semantics(), testTag(), clearAndSetSemantics(). 그리기: drawBehind(), drawWithContent(), drawModifier() — 캔버스에 사용자 정의 그리기를 허용하는 수정자.

의미론 수정자는 특수 카테고리입니다. Modifier.semantics {}는 접근성 트리에서 요소가 표현되는 방식을 정의합니다. Compose는 텍스트에서 자동으로 의미론을 채우지만, 사용자 정의 구성 요소는 역할, 상태 및 작업을 수동으로 설정해야 합니다. 이는 WCAG 2.2 준수와 TalkBack(Android) 및 VoiceOver(iOS)의 올바른 작동에 중요합니다.

kotlin
@Composable
fun ModifierDemo() {
    // 올바른 순서의 수정자 체인
    Box(
        modifier = Modifier
            .size(150.dp)
            .padding(8.dp)
            .border(2.dp, Color.Gray)
            .background(Color(0xFFE3F2FD))
            .clickable { /* handle click */ }
            .semantics {
                contentDescription = "Demo card with click action"
                role = Role.Button
            }
    ) {
        Text("터치하세요")
    }
}

Modifier.composed를 통한 사용자 정의 수정자 생성

Modifier.composed는 팩토리 메서드로, 다른 수정자, LocalComposition 및 로컬 상태를 사용할 수 있는 복합 수정자를 생성합니다. 일반 확장 함수와 달리 composed는 적용될 때마다 인스턴스를 생성하므로 수정자가 자체 상태를 가질 수 있습니다.

composed 사용 시기: 반복되는 수정자 조합(예: 표준 카드 스타일: padding + background + border + clickable); 상태가 있는 수정자(누를 때 애니메이션 배경 변경); CompositionLocals 접근(MaterialTheme 색상 체계, 픽셀 밀도). 일반적인 경우 composed 없는 일반 확장 함수로 충분합니다.

composed의 성능: 각 호출이 새 수정자 객체를 생성하므로 재구성 중 추가 할당이 발생할 수 있습니다. 이를 방지하려면 composed를 remember로 감싸세요. Google은 내부에 실제로 상태 또는 CompositionLocal이 필요한 경우에만 composed를 사용할 것을 권장합니다. 정적 조합에는 일반 확장 함수를 사용하세요.

kotlin
// 상태가 있는 composed를 통한 사용자 정의 수정자
fun Modifier.cardStyle(
    elevation: Dp = 4.dp,
    isSelected: Boolean = false
): Modifier = this.composed {
    val backgroundColor = if (isSelected)
        MaterialTheme.colorScheme.primaryContainer
    else
        MaterialTheme.colorScheme.surface

    this
        .fillMaxWidth()
        .padding(12.dp)
        .background(backgroundColor, RoundedCornerShape(8.dp))
        .shadow(elevation, RoundedCornerShape(8.dp))
}

// 사용 예
@Composable
fun CardList() {
    Column {
        Box(Modifier.cardStyle()) { Text("항목 1") }
        Box(Modifier.cardStyle(isSelected = true)) { Text("선택됨") }
    }
}

// 정적 버전(composed 없음) — 더 빠름
fun Modifier.simpleCardStyle(): Modifier =
    this.fillMaxWidth().padding(8.dp).clip(RoundedCornerShape(4.dp))

Modifier 성능 및 모범 사례

매 재구성마다 Modifier를 다시 생성하지 마세요. 수정자가 가변 데이터에 의존하지 않는 경우 상수 또는 remember로 이동하세요. Modifier.padding().background() 호출 시마다 새 Modifier.Element 객체가 생성됩니다. 독립된 구성 요소에서는 무시할 수 있지만, 수백 개의 요소가 있는 LazyColumn에서는 추가 할당이 스크롤 시 눈에 띄는 지연을 유발합니다.

규칙: 수정자 체인이 Composable 함수의 매개변수에 의존하지 않는 경우 함수 외부(파일 또는 Companion 수준)에서 val로 선언하세요. 의존하는 경우 remember(의존성) { ... }를 사용하세요. 항상 동일한 수정자의 경우 Composable 외부의 val이 가장 효율적입니다. 이러한 객체는 애플리케이션 전체 수명 동안 한 번만 생성됩니다.

Modifier 순서 모범 사례: 수정자를 논리적 순서로 배치하세요. 먼저 크기/패딩(레이아웃), 다음 장식(background, border), 마지막 동작(clickable, pointerInput). 이는 가독성을 향상시킬 뿐만 아니라 Compose Runtime이 측정 중에 체인을 최적화하는 데 도움이 됩니다. 또한 다른 Modifier로 과도하게 중첩된 Box를 피하세요. 종종 부모 컨테이너의 단일 Modifier가 2-3개의 중첩을 대체할 수 있습니다.

kotlin
// ✅ 좋음: Composable 외부의 상수
private val cardModifier = Modifier
    .fillMaxWidth()
    .padding(16.dp)
    .clip(RoundedCornerShape(8.dp))

@Composable
fun CardContent() {
    Box(cardModifier.background(Color.White)) { ... }
}

// ❌ 나쁨: 매 재구성마다 재생성
@Composable
fun BadCard() {
    Box(Modifier.fillMaxWidth().padding(16.dp)) { ... }
}

// ✅ 좋음: 동적 Modifier에 remember 사용
@Composable
fun DynamicCard(color: Color) {
    val modifier = remember(color) {
        Modifier.fillMaxWidth().background(color)
    }
    Box(modifier) { ... }
}

자주 묻는 질문

여러 Composable 요소에 동일한 Modifier를 사용할 수 있나요?

네, Modifier는 불변이므로 하나의 객체를 여러 곳에서 안전하게 사용할 수 있습니다. 그러나 composed 수정자를 사용하는 경우 각 호출이 새 인스턴스를 생성합니다. 정적 체인의 경우 Composable 외부의 상수 또는 val이 최적의 솔루션입니다.

수정자 체인을 디버그하려면 어떻게 하나요?

Android Studio의 Layout Inspector를 사용하세요. 각 Modifier의 경계를 시각적으로 보여줍니다. 프로그래밍 방식 디버깅의 경우 체인의 각 단계에 다른 색상의 Modifier.border()를 추가하여 각 수정자의 경계를 확인하세요.

Modifier.then()이란 무엇이며 순차 호출과 어떻게 다른가요?

Modifier.then(other)는 other 체인을 this에 추가합니다. 순차 호출(Modifier.a().b())은 Modifier.then(a()).then(b())와 동일합니다. 차이가 없습니다 — 동일한 체인 메커니즘입니다. then()은 변수에서 기성 체인을 추가해야 할 때 유용합니다.

Modifier가 접근성 의미론에 어떤 영향을 미치나요?

Modifier.semantics {}는 요소가 스크린 리더에 설명되는 방식을 정의합니다. Modifier.clickable()은 자동으로 버튼 역할과 Action(OnClick)을 추가합니다. 사용자 정의 제스처의 경우 명시적으로 semantics를 지정해야 합니다. 의미론 수정자가 없으면 TalkBack 사용자가 사용자 정의 구성 요소와 상호 작용할 수 없습니다.

Modifier의 background가 둥근 모서리에서 작동하지 않는 이유는 무엇인가요?

Modifier.background(color, shape)는 모서리와 함께 작동하지만, 모서리를 자르려면 clip()이 background보다 먼저 와야 합니다. 올바른 순서: clip(shape).background(color). 내부 콘텐츠도 잘라야 하는 경우 부모에서 clipToBounds()를 사용하세요.

요약

  • Modifier는 모양과 동작을 선언적으로 설명하는 불변 객체
  • 순서가 결과를 결정: padding → clickable vs clickable → padding
  • 체인은 순차적으로 구축, 각 요소가 이전 요소를 감쌈
  • Modifier.composed로 상태와 CompositionLocal을 가진 수정자 생성 가능
  • 성능: 정적 체인은 상수로, 동적 체인에는 remember 사용
  • 의미론: 사용자 정의 구성 요소의 접근성에 Modifier.semantics 필수
  • 권장: 수정자를 레이아웃 → 장식 → 동작 순으로 배치

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기