Jetpack Compose의 커스텀 Modifier
Jetpack Compose의 커스텀 Modifier
Jetpack Compose에서는 커스텀 Modifier를 생성하는 여러 가지 방법을 제공하며, 각 접근 방식은 단순성, 유연성, 성능 사이에서 서로 다른 트레이드오프를 가집니다. 적합한 방식을 선택하려면 Compose 상태(state)에 접근할 필요가 있는지, Modifier가 얼마나 자주 재평가되는지, 그리고 드로잉이나 레이아웃에 대한 저수준 제어가 필요한지를 종합적으로 고려해야 합니다. 이번 면접 질문을 통하여 아래 내용들을 학습하실 수 있습니다.
- Modifier 팩토리, 컴포저블 Modifier 팩토리,
Modifier.Node기반 구현의 차이를 구분할 수 있습니다. - Modifier 팩토리가 호이스팅(hoisting) 가능하며 리컴포지션(Recomposition) 시 메모리 할당이 발생하지 않는 이유를 설명할 수 있습니다.
- 컴포저블 Modifier 팩토리가 필요한 상황과 그로 인해 발생하는 제약 사항을 설명할 수 있습니다.
Modifier.Node구현의 세 가지 구성 요소인 팩토리, 엘리먼트, 노드의 역할을 분석할 수 있습니다.- 주어진 사용 사례(use case)에 적합한 Modifier 접근 방식을 선택할 수 있습니다.
Modifier 팩토리
Modifier 팩토리는 Modifier의 일반 확장 함수(extension function)로, 기존 Modifier들을 체이닝하여 조합하는 방식입니다. Compose 상태, 애니메이션, CompositionLocal 등을 사용하지 않으며, 순수한 코틀린 함수이기 때문에 리컴포지션 시 메모리 할당이 전혀 발생하지 않고, 자유롭게 호이스팅할 수 있습니다.
fun Modifier.myBackground(color: Color): Modifier =
this
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
.background(color)
사용 방법은 매우 간단합니다.
Box(modifier = Modifier.myBackground(Color.Cyan))
이 패턴은 스타일링 단축키나 레이아웃 프리셋처럼, Modifier 구성이 정적이거나 호출 시점에 전달된 매개변수로부터 완전히 파생되는 경우에 가장 적합합니다. 별도의 상태 관리가 필요 없는 UI 스타일 조합에 이 방식을 우선적으로 고려하시는 것을 권장합니다.
컴포저블 Modifier 팩토리
Modifier 내부에서 Compose 상태를 읽거나, 값을 애니메이션하거나, CompositionLocal 값에 접근해야 하는 경우에는 팩토리 함수에 @Composable 어노테이션을 붙여야 합니다. 이렇게 하면 컴포지션 스코프에 접근할 수 있지만, 그에 따른 비용이 수반됩니다. 해당 함수는 컴포지션 외부로 호이스팅할 수 없으며, 리컴포지션이 발생할 때마다 항상 재평가됩니다.
@Composable
fun Modifier.fade(enabled: Boolean): Modifier {
val alpha by animateFloatAsState(
if (enabled) 0.5f else 1f
)
return this.then(
Modifier.graphicsLayer { this.alpha = alpha }
)
}
컴포저블 함수 내에서의 사용 예시는 다음과 같습니다.
Box(modifier = Modifier.fade(enabled = isDimmed))
이 함수는 컴포지션에 참여하므로, 함수 내부에서 읽는 상태가 변경되면 호출 지점의 리컴포지션이 트리거됩니다. 시간이 지남에 따라 변하는 애니메이션 값이나 관찰 가능한 값에 의존하는 Modifier에 이 접근 방식을 사용하시면 됩니다.
Modifier.Node
Modifier.Node는 렌더링, 레이아웃, 제스처 처리, 상태 위임에 대한 완전한 제어를 제공하는 저수준 API입니다. 세 가지 구성 요소로 이루어져 있으며, 각각의 역할은 다음과 같습니다.