젯팩 컴포즈 스터디를 이어가본다. 아래는 지난 젯팩 컴포즈 포스팅에서 다뤘던 내용과 이번에 다룰 내용 Chapter 정보이다.
지난 스터디에서는 젯팩 컴포즈의 가장 기본적인 빌딩블럭인 '컴포저블 함수'에 대한 개념과 컴포저블 함수의 기본적인 동작 매커니즘에 대해 이해할수 있었고, 이번 스터디에서는 UI 컴포넌트를 동적으로 구성하기 위한 컴포저블 Slot API 사용법부터, 공통의 컴포넌트 속성변경을 위한 Modifier 마지막으로 이러한 컴포넌트를 행/열에 배치하기 위한 Row/Column 에 대해 알아본다.
- 지난 스터디 내용
- Ch18 컴포즈 개요
- Ch19 컴포저블 함수 개요
- Ch20 컴포즈 상태와 재구성
- Ch21 CompositionLocal
- 이번 스터디 내용
- Ch22 컴포즈 Slot API
- Ch23 컴포즈 Slot API 튜토리얼
- Ch24 모디파이어 이용하기
- Ch25 Row/Column 이용해 레이아웃 구성하기
안드로이드 젯팩 컴포즈 스터디 (ch18~21)
이전 포스팅에서는 젯팩 컴포즈 스터디를 시작하게 된 동기를 작성했다.지금부터는 본격적으로 젯팩 컴포즈에 대해 스터디를 해보자. 이번 포스팅에서 다룰 챕터는 다음과 같다.Ch18 컴포즈 개
sparkthenmove.tistory.com
Ch22 컴포즈 Slot API
컴포저블 함수는 하나의 이상의 (자식) 컴포저블 함수를 호출할 수 있다. 아래는 SlotDemo 라는 컴포저블 함수를 통해 3개의 TextView 를 구성하는 예제코드이다. SlotDemo 컴포저블은 하위에 무조건 3개의 Text 컴포저블을 가진다.
@Composable
fun SlotDemo() {
Column {
Text("Text1")
Text("Text2")
Text("Text3")
}
}
이때, SlotDemo 컴포저블 내부의 3개의 하위 컴포저블중 2번째 컴포저블은 런타임에 컴포저블의 종류가 결정되도록 할수 있을까? 런타임에 결정되도록 하려면, 하드코딩하지 않고, 어떤 컴포저블이 사용될지 런타임에 함수의 인자로 받으면 된다. 이렇게 실행시점에 다른 컴포저블로 배치할수 있도록 슬랏(Slot)을 열어두어야 하는데, 이것을 컴포저블에 대한 Slot API를 제공한다고 부른다.
Slot API 선언하는 방법
이름이 Slot API 라서 생소할뿐, 일반적인 프로그래밍에서 함수를 인자로 넘기는 것을 생각하면 된다. 이 함수가 컴포저블 함수인것 뿐이다. Gemini 에 더블체크를 해보았다.

젯팩 컴포즈의 컴포저블 함수에서 Slot API 를 선언하는 코드예시를 보자. param 이라는 함수 파라미터는 컴포저블 함수를 인자로 받는다. param 콜론 뒤에 타입 정의한 부분은 예제코드 아래에 의미를 꼼꼼하게 적어봤다.
@Composable
fun SlotDemo(param: @Composable () -> Unit) {
Column {
Text("Text1")
param() // <-- 함수인자로 전달받은 컴포저블 함수 호출
Text("Text3")
}
}
@Composable () -> Unit 의 각 부분에 대한 자세한 의미
- () (입력 파라미터):
- 의미: 이 람다 함수는 아무런 인자(매개변수)도 받지 않습니다.
- -> (구분자):
- 의미: 입력과 출력을 구분하는 화살표입니다.
- Unit (반환 타입):
- 의미: 이 람다 함수는 코틀린에서 실질적인 값을 반환하지 않음을 나타냅니다. (Java의 void와 유사하며, 컴포저블은 UI를 구성하는 것이 주 목적이므로 일반적으로 Unit을 반환합니다.)
- @Composable (한정자/Qualifier):
- 의미: 이 함수 타입은 일반적인 함수 타입이 아니라, 컴포즈의 컴포지션 컨텍스트 내에서만 호출될 수 있는 특별한 함수 타입임을 나타냅니다.
param: @Composable () -> Unit은 "아무 인자도 받지 않고 아무 값도 반환하지 않지만, @Composable 함수만을 포함할 수 있는 람다 함수"를 param으로 받겠다는 의미입니다.
다음으로는 SlotDemo 에 컴포저블 함수를 넘기는 코드를 살펴보자. param 에는 { ButtonDemo() } 람다 함수를 전달한다. SlotDemo 컴포저블 함수 내부에서 param() 과 같이 호출하면 { ButtonDemo() }() 와 같이 호출되고, 람다함수 내부의 유일한 컴포저블 함수인 ButtonDemo() 가 이때 실행되면서 Button 이 렌더링 된다.
@Composable
fun ButtonDemo() {
Button(onClick = { }) {
Text("Click me")
}
}
// SlotDemo 의 param 에는 { ButtonDemo() } 라는 람다함수가 전달된다.
SlotDemo(param = { ButtonDemo() })
정리하면 Slot API 를 사용하면 컴포저블 함수의 내용을 호출시점에(런타임) 동적으로 지정할 수 있다.
Ch23 컴포즈 Slot API 튜토리얼
이전 Chapter 에서 배운 Slot API 를 직접 구현해보자. Slot API 는 컴포저블 함수의 파라미터로 컴포저블 함수를 넘기기 위한 API이다. 이렇게 컴포저블 함수를 넘기는 이유는, 호출당한 컴포저블 함수에서 UI 를 동적으로 구성하기 위함이다.
이번 챕터는 튜토리얼이라서, 책에 있는 코드를 쭉 따라하면 되는데, 핵심코드는 다음과 같다.
ScreenContent 컴포저블 함수를 호출하는데, 함수의 인자로 titleContent, progressContent 에 컴포저블


실행결과는 다음과 같다. Image Title 체크버튼의 활성화 여부에 따라 앱 상단에 이미지 또는 텍스트가 컴포넌트가 놓이게 된다. 또한 Linear Progress 체크버튼 활성화 여부에 따라 앱 중앙부분에 프로그레스바가 직선 또는 원형의 컴포넌트가 놓이게 된다.
이와 같이 특정영역의 UI 컴포넌트를 동적으로 배치하고자 할때 Slot API 를 사용하면 된다.
![]() |
![]() |
![]() |
![]() |
Ch24 모디파이어 이용하기
이전 Chapter 의 예제코드를 보다보면 Modifier 를 사용하는 코드를 봤을것이다. 뭔지 잘 모르겠지만 그냥 따라서 타이핑 했을것이다. 모디파이어가 뭘까?
Modifier 란?
컴포즈 내장 객체이며, 컴포저블에 적용될 수 있는 속성을 가지고 있다.
컴포저블 함수에 Modifier 를 파라미터로 전달하여, 테두리, 패딩, 배경, 크기와 같은 설정을 변경할 수 있다.
Modifier 객체의 프로퍼티 변경 메서드를 chain 형태로 연결하여 많이 사용한다. 이때 주의할 점은 메서드를 chain 으로 연결시 호출순서대로 속성이 설정된다는 점이다. 지금은 padding 먼저 적용 후, border 가 이후에 적용된다. chain 순서를 변경하면 border 가 먼저 적용되고, padding 이 이후에 적용된다.
val modifier = Modifier
.padding(all = 10.dp)
.border(width = 2.dp, color = Color.Black)
// 위에서 생성한 modifier 를 Text 컴포넌트에 적용. (마진과 border 가 적용된다)
Text(
"hello compose",
modifier = modifier,
fontSize = 40.sp,
fontWeight = FontWeight.Bold
)
공통 내장 Modifier
기본적으로 젯팩 컴포즈에서 제공하는 Modifier 의 메서드는 매우 많다 (100여개) 구글 개발자 싸이트에서 확인할 수 있다.

이중에서 널리 사용되는 함수들 위주로 정리한다.
- background: 컴포저블의 배경에 색상 적용
- clickable: 컴포저블 클릭시 호출되는 핸들러 지정 (클릭하면 깜박임)
- clip: 컴포저블의 컨텐츠를 지정한 형태로 자른다 (사각형 모서리를 둥글게 한다든지)
- fillMaxHeight: 컴포저블의 높이를 부모가 허용하는 최대값으로 만듬
- fillMaxWidth: 컴포저블의 폭을 부모가 허용하는 최대값으로 만듬
- fillMaxSize: 컴포저블의 높이/폭을 부모가 허용하는 최대값으로 만듬
- layout: 커스텀 레이아웃 행동 구현시 사용
- offset: 컴포저블을 현재위치에서 x, y 축 방향으로 이동
- padding: 컴포저블 주변에 공백 추가
- rotate: 컴포저블 중심 기준으로 지정한 값만큼 회전
Modifier 객체를 생성하고 프로퍼티 변경을 위한 메서드를 chain 형태로 호출하면 된다고 설명했는데, 2개이상의 modifier 객체를 조합하는 것도 가능하다. 그러면 앞에 있는 modifier 객체의 프로퍼티를 먼저 적용하고, 2번째 modifier 객체의 프로퍼티가 적용된다.
val combinedModifier = firstModifier.then(secondModifier).then(thirdModifier) ...
컴포저블에 modifier 를 넘길때 파라미터 위치는 정해져있다. 바로 "함수의 파라미터 리스트의 첫번째 선택적 파라미터" 자리이다.
Ch25 Row/Column 이용해 레이아웃 구성하기
이번에는 버튼, 이미지, 텍스트 자체가 아니라, 이러한 UI 컴포넌트들을 화면에 "배치"하는 방법에 대해 알아본다. 챕터의 제목을 보고 유추할 수 있듯이 행(Row)과 열(Column) 컴포저블을 사용하여 UI 컴포넌트들을 화면에 원하는 형태로 배치할 것이다. 자세한 내용은 아래에서 설명하겠지만, 결론부터 얘기하면 Row 컴포저블은 UI 컴포넌트들을 가로방향으로 배치한다. 반대로 Column 컴포저블은 내부의 UI 컴포넌트들을 세로방향으로 배치한다. Row와 Column을 조합하면 UI 컴포넌트를 원하는대로 배치할수 있다.
예시코드를 보면 직관적으로 쉽게 이해가 된다. Android Studio 에서 새로운 앱 코드작성을 위해 신규 모듈을 Empty Activity 형태로 추가한 후, 기본적으로 제공되는 Greeting 컴포저블 함수 대신, MainScreen 컴포저블 함수로 변경하고 아래 코드를 추가하자. MainScreen 컴포저블안에서 행(가로)방향으로 UI 컴포넌트를 배치하는 Row 레이아웃 컴포저블을 호출한다. 그리고 Row 내부에 3개의 TextCell 컴포저블을 호출하면, 3개의 TextCell 이 가로방향으로 배치된다.
...
@Composable
fun MainScreen() {
Row {
TextCell("1")
TextCell("2")
TextCell("3")
}
}
@Composable
fun TextCell(text: String, modifier: Modifier = Modifier) {
val cellModifier = Modifier
.padding( 4.dp)
.size(100.dp, 100.dp)
.border(width = 4.dp, color = Color.Black)
Text(text = text,
cellModifier.then(modifier),
fontSize = 70.sp,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center)
}
다음 실행결과를 보면 1, 2, 3 TextCell 이 가로방향으로 나열되어 있음을 확인할 수 있다. Row 대신 Column 을 사용할 경우, 쉽게 상상할수 있듯이 1, 2, 3 TextCell 이 세로방향으로 나열된다.
![]() |
![]() |
Row와 Column 을 조합하면 다음과 같이 TextCell 을 배치하는 것이 가능하다. 왼쪽의 가상 디바이스에 보이는 TextCell 은 우측과 같이 Row/Column 컴포저블 구조 기반으로 렌더링 된다.
![]() |
![]() |
정렬(Alignment)
레이아웃에서 중요한 것중 한가지는 "정렬"이다. 아래는 Row 컴포저블의 파라미터로 verticalAlignment 에 수직라인 기준 정렬속성을 설정하는 코드이다. 젯팩 컴포즈는 centerVertically 외에도 여러가지 정렬을 지원한다.
- 수직방향
- Alignment.Top: 수직방향 제일 위
- Alignment.CenterVertically: 수직방향 중앙
- Alignment.Bottom: 수직방향 제일 아래
- 수평방향
- Alignment.Start: 수평방향 제일 왼쪽
- Alignment.CenterHorizontally: 수평방향 중앙
- Alignment.End: 수평방향 제일 오른쪽
@Composable
fun MainScreen() {
Row (verticalAlignment = Alignment.CenterVertically, // <-- 수직라인 기준으로 정렬
modifier = Modifier
.size(width=400.dp, height=200.dp)
.border(width = 3.dp, color = Color.Red)) {
TextCell("1")
TextCell("2")
TextCell("3")
}
}

배열(Arrangement)
앞서 살펴본 정렬(Alignment)과 다르게 이번에는 배열(arrangement)을 살펴보자. 배열도 정렬과 속성이름이 유사하다.
- 수직방향
- Arrangement.Top: UI 컴포넌트를 수직방향 시작(위)위치에 정렬
- Arrangement.Center: UI 컴포넌트를 수직방향 중앙에 정렬
- Arrangement.Bottom: UI 컴포넌트를 수직방향 끝(아래)위치에 정렬
- 수평방향 배열
- Arrangement.Start: UI 컴포넌트를 수평방향 시작(왼쪽)위치에 정렬
- Arrangement.CenterHorizontally: UI 컴포넌트를 수평방향 중앙에 정렬
- Arrangement.End: UI 컴포넌트를 수평방향 끝(오른쪽)위치에 정렬

배열 간격을 조정하는 속성도 존재한다. 아래 3가지는 모두 자식 컴포넌트를 균등간격으로 배치한다. 하지만 제일 앞과 뒤를 어떻게 다루는지가 차이점이다. 아래 속성과 UI 컴포넌트 배치된 결과를 함께 살펴보면 이해가 쉽게 될 것이다.
- Arrangement.SpaceEvenly: 자식 컴포넌트는 균등간격, 첫번째 자식 앞, 마지막 자식 뒤 공간 포함
- Arrangement.SpaceBetween: 자식 컴포넌트는 균등간격, 첫번째 자식 앞, 마지막 자식 뒤 공간 미포함
- Arrangement.SpaceAround: 자식 컴포넌트는 균등간격, 첫번째 자식 앞, 마지막 자식 뒤 공간은 자식사이 공간의 절반



Row 또는 Column 의 자식 컴포저블은 부모의 Scope 에 있다고 보통 말한다. 이때 2가지 RowScope, ColumnScope 은 추가의Modifier 함수를 제공하여, 자식 컴포저블의 동작/형태를 변경할 수 있다. 아래 코드와 실행결과를 보면 쉽게 이해할 수 있다.
@Composable
fun MainScreen() {
Row (modifier = Modifier.height(300.dp)) {
// RowScope 은 자식들이 이용할수 있는 Modifier 를 제공.
TextCell("1", Modifier.align(Alignment.Top))
TextCell("2", Modifier.align(Alignment.CenterVertically))
TextCell("3", Modifier.align(Alignment.Bottom))
}
}
실행결과

이외에도 Modifier.alignByBaseline() 을 사용하면, 서로다른 크기의 텍스트간 베이스라인(텍스트 하단)을 맞춰서 정렬한다.
또한 안드로이드 앱 화면 크기에 관계없이 여러개의 UI 컴포넌트의 상대적 비율값을 설정하여 크기를 결정하는 weight 도 지원한다.
이상으로 Ch22 부터 25까지 쭉 살펴봤는데, 아직까지는 개념적으로 이해하기 어려운 부분은 없었다. 다만, 젯팩이 제공하는 다양한 컴포저블의 종류와 Modifier 에서 제공하는 속성값을 어느정도 숙지하고 내가 원하는 배치를 쉽게 적용하기 위해서는 반복적으로 연습이 좀 필요할 것 같다. (바이브코딩을 통해 API를 숙지하지 않아도 어느정도는 커버가 되긴 하겠지만)
Spark Then Move !
'안드로이드 앱' 카테고리의 다른 글
| 젯팩 컴포즈에서 XYZScope 의미와 동작원리 (RowScope, ColumnScope, BoxScope) (0) | 2025.11.28 |
|---|---|
| 안드로이드 젯팩 컴포즈 (ch26~28) - Box레이아웃, 커스텀 레이아웃 (0) | 2025.11.28 |
| 안드로이드 젯팩 컴포즈 (ch18~21) - 컴포저블 개념, 상태 및 재구성 (0) | 2025.11.26 |
| 안드로이드 젯팩 컴포즈 스터디 시작. (0) | 2025.11.23 |
| 안드로이드 앱 개발에 필요한 모든 것 (목차) (0) | 2025.11.22 |







