[android] Compose 정리 (2) - Modifier

vhv3y8·2025년 1월 5일

Android Jetpack Compose

목록 보기
3/3

일단 Composable에서 무슨 Modifier를 쓸 수 있는지 명확히 알기 위해서는 스코프에 대해 알 필요가 있다.

스코프 인터페이스

기본적으로 Modifier 확장 함수들이 제공되지만,

많은 Composable들은 자신에서만 쓸 수 있는 기능들을 제공하기 위해 자신의Scope 인터페이스를 제공하기도 한다.

@Composable
inline fun Row(
    modifier: Modifier = Modifier,
    horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
    verticalAlignment: Alignment.Vertical = Alignment.Top,
    content: @Composable RowScope.() -> Unit
): Unit

androidx.compose.foundation.layout에 있는 Row Composable을 보면, 마지막 파라미터로 @Composable RowScope.() -> Unit 타입의 함수(trailing lambda)를 받는다.

RowScope.() 이런 문법은 receiver라고 부른다고 하며, 간단하게 보면 함수 내부에서 RowScope에 있는 프로퍼티, 메서드들을 바로 접근할 수 있게 해준다.

interface RowScope {
    fun Modifier.weight(/* ... */): Modifier
    /* ... */
}

예를 들면 이처럼 Modifier.weight() 함수는 RowScope 인터페이스 안에 정의되어 있고, 그래서 Row 안의 Modifier에서는 .weight()를 체이닝할 수 있다. (코드를 보면 인스턴스도 같은 파일에 생성되어있다)

물론 스코프 인터페이스에서 정의된 확장 함수들도 문서에 잘 정리되어있다.

결론적으로,

  1. 기본적인 Modifier 확장 함수들
  2. 각 Composable이 제공하는 Scope 인터페이스에서 정의된 Modifier 함수들
    • 위처럼 Composable의 파라미터에서 넘겨주는 Scope를 보면 알 수 있다.

이 둘을 살펴보면 사용할 수 있는 Modifier를 알 수 있다.

주요 Modifier 함수들

우선 모든 Composable은 사실 Layout이라는 @Composable 위에 Modifier를 쌓은 것이라는 점을 알아야 한다. (androidx.compose.ui.layoutLayout)

Box, Row, Column 같은 Composable부터, 그들을 사용해 쌓아올린 Material3 Composable들까지 모두가 해당된다.

쌓는 과정에서 public인 Modifier 함수만 사용하는 건 아니지만, public인 경우가 많다.

그리고 Modifier 함수들도 내부적으로 다른 Modifier 함수들을 사용하고 있는 경우가 많다.

즉, 모든 Modifier 함수들을 이해해야만 사용할 수 있지는 않다는거다.

뼈대를 이루는 함수들도 모두 리스트에 함께 있지만, 여러 겹 덧붙여진 함수들만 알아도 기본적인 사용에는 충분한 것이다.

자세한 리스트는 List of Compose modifiers (developer.android.com) 참고

그런 함수들 중 일부만 알아보자.

크기

  • Modifier.width(width: Dp)
  • Modifier.height(height: Dp)
  • Modifier.size(size: Dp)

required는 크기가 되어야 할 정확한 값을 주며 덮어씌운다 :

  • Modifier.requiredWidth(width: Dp)
  • Modifier.requiredHeight(height: Dp)
  • Modifier.requiredSize(size: Dp)

fillMax는 가능한 최대로 크기를 설정한다 :

  • Modifier.fillMaxHeight(fraction: @FloatRange(from = 0.0, to = 1.0) Float)
  • Modifier.fillMaxWidth(fraction: @FloatRange(from = 0.0, to = 1.0) Float)
  • Modifier.fillMaxSize(fraction: @FloatRange(from = 0.0, to = 1.0) Float)

wrapContent는 내부 content를 감싸도록 조정한다 :

  • Modifier.wrapContentHeight(align: Alignment.Vertical, unbounded: Boolean)
  • Modifier.wrapContentSize(align: Alignment, unbounded: Boolean)
  • Modifier.wrapContentWidth(align: Alignment.Horizontal, unbounded: Boolean)

등등 (이후 생략)

위치

  • Modifier.offset(x: Dp, y: Dp)
  • Modifier.absoluteOffset(x: Dp, y: Dp)

Padding

  • Modifier.padding(all: Dp)
  • Modifier.padding(horizontal: Dp, vertical: Dp)
  • Modifier.padding(start: Dp, top: Dp, end: Dp, bottom: Dp)

Border

  • Modifier.border(width: Dp, color: Color, shape: Shape)

배경

  • Modifier.background(color: Color, shape: Shape)
  • Modifier.alpha(alpha: Float)
  • Modifier.shadow(elevation: Dp, shape: Shape, clip: Boolean, ambientColor: Color, spotColor: Color)
  • Modifier.blur(radius: Dp, edgeTreatment: BlurredEdgeTreatment)
  • Modifier.clip(shape: Shape)
  • Modifier.clipToBounds()

정렬

  • Modifier.align(alignment: Alignment.Vertical) (RowScope)
  • Modifier.align(alignment: Alignment.Horizontal) (ColumnScope)

레이아웃

해당 element가 어떻게 그려질지 하나만 조정하게 해준다 :

  • Modifier.layout(measure: MeasureScope.(Measurable, Constraints) -> MeasureResult)

제스처

  • Modifier.clickable(enabled: Boolean, onClickLabel: String?, role: Role?, onClick: () -> Unit)
  • Modifier.combinedClickable(enabled: Boolean, onClickLabel: String?, role: Role?, onLongClickLabel: String?, onLongClick: (() -> Unit)?, onDoubleClick: (() -> Unit)?, onClick: () -> Unit) (@ExperimentalFoundationApi)
  • Modifier.draggable(state: DraggableState, orientation: Orientation, enabled: Boolean, interactionSource: MutableInteractionSource?, startDragImmediately: Boolean, onDragStarted: suspend CoroutineScope.(startedPosition: Offset) -> Unit, onDragStopped: suspend CoroutineScope.(velocity: Float) -> Unit, reverseDirection: Boolean)
  • ...

등등 아주 많은 Modifier 함수들이 있다.

Modifier 적용 순서

Image(
  painterResource(R.drawable.frag),
  contentDescription = null,
  Modifier
    .clip(CircleShape)
    .padding(10.dp)
    .size(100.dp)
)

이런 Image Composable이 있을 때, UI 트리에서의 노드는 다음과 같이 감싸지며 구성된다 :

 바깥                        내부
clip -> padding -> size -> Image

여기서 각 Modifier들도 노드 객체가 되며(Modifier.Node), Image()의 노드가 이런 식으로 구성되는 것이다.

Layout Phase에서는, Modifier 노드도 레이아웃과 연관이 있는 경우(e.g. .padding()) Composable들과 마찬가지로 높이, 너비, 위치 값을 갖는다고 한다. (아마도 LayoutModifierNode)

예를 들어 Modifier.wrapContentSize()는 내부에 있는 Child를 가운데로 배치시키는데, 이는 .wrapContentSize()에 대한 노드(WrapContentNode)가 트리에 실제로 존재하고 높이, 너비, 위치 값들을 갖기 때문에 가능한 것이다.

Layout Phase에서 Constraints를 통해 각자 높이, 너비, 위치를 계산하는 과정(measure)은 바깥에서 안으로 들어가며 이루어지고, 값을 결정하고 배치하는 단계(layout)는 안에서 밖으로 나가며 이루어진다고 한다.

참고로 이 과정은 노드의 MeasureScope.measure() 함수 내부에서 재귀와 비슷하게 이루어지는듯하다.

한 줄 요약하면 Modifier chain은 작성한 순서대로 적용되며, Composable은 트리에서 체인 가장 내부에 존재한다.

정확한 내용은 문서와 영상, 소스코드를 참고하자.

참고

Modifier 목록

Modifier 적용 순서

profile
개발 기록, 미래의 나에게 설명하기

0개의 댓글