2025. 5. 3. 20:53ㆍApp Dev/Android Studio
영천대환샘 | 수천대환샘 | EMF |
Android Studio
1. Codelab 시작하기
https://developer.android.com/get-started/overview?hl=ko
첫 앱 빌드 | Get started | Android Developers
Get started building your Android apps.
developer.android.com
🧭 Codelab 순서도: 첫 번째 Android 앱 만들기
[1] Android 스튜디오 설치
↓
[2] 새 프로젝트 생성 (템플릿 사용)
↓
[3] 앱 실행 (미리보기 도구 사용)
↓
[4] 코드 수정 (Kotlin으로 텍스트 변경)
↓
[5] UI 수정 (Jetpack Compose 사용)
↓
[6] Compose 미리보기로 확인
↓
[7] 소개 앱 완성 (사용자 이름 등 맞춤)
🔍 요약 포인트
1️⃣ | Android 스튜디오 설치 및 시스템 요구사항 확인 |
2️⃣ | 템플릿 선택으로 새 프로젝트 시작 |
3️⃣ | 미리보기 도구를 통한 앱 실행 확인 |
4️⃣ | Kotlin으로 앱 텍스트 수정 |
5️⃣ | Jetpack Compose로 UI 구성 |
6️⃣ | Compose 미리보기로 실시간 반영 확인 |
7️⃣ | 사용자 이름이 들어간 소개 앱 완성 |
- android: 안드로이드 개발에 최적화된 보기로, 앱 구성 요소만 간단히 보여줍니다.
- project (source files): 전체 프로젝트 구조를 파일 시스템 그대로 보여주며, 모든 파일과 폴더에 접근할 수 있습니다.
✅ package
- 의미: 클래스, 인터페이스, 함수 등을 논리적으로 묶는 폴더 같은 개념입니다.
- 사용 이유: 코드의 조직화와 이름 충돌 방지를 위해 사용됩니다.
- 이 코드는 이 파일이 com.example.greetingcard 패키지 안에 있음을 의미한다.
✅ import
- 의미: 다른 패키지에 있는 클래스나 함수를 현재 파일에서 사용 가능하게 불러오는 것입니다.
- 사용 이유: 코드를 재사용하고, 긴 경로를 계속 반복하지 않기 위해.
- import androidx.compose.material3.Text때문에 Text() 컴포저블을 직접 사용할 수 있다.
✅ class
- 의미: 객체를 생성하기 위한 설계도 또는 틀입니다.
- 구성요소: 속성(변수)과 기능(메서드)을 가질 수 있어요.
✅ @Composable
- 의미: Jetpack Compose에서 UI 요소를 그리는 함수임을 표시하는 애노테이션입니다.
- 역할: 이 애노테이션이 있어야 Compose가 그 함수를 UI로 사용할 수 있어요.
✅ @Preview
- 의미: Android Studio에서 컴포저블 함수를 미리보기 할 수 있게 하는 애노테이션입니다.
- 사용 조건: @Composable 함수 위에 같이 써야 해요.
✅ MainActivity란?
- Activity는 Android 앱에서 하나의 화면을 의미해요.
- MainActivity는 앱을 실행했을 때 가장 먼저 실행되는 Activity입니다.
- MainActivity는 보통 AppCompatActivity 또는 ComponentActivity를 상속받습니다.
- Jetpack Compose에서는 보통 ComponentActivity를 상속해서 Compose UI를 그립니다.
✅ ComponentActivity란?
- **androidx.activity.ComponentActivity**는 Android의 Activity를 기반으로 확장된 클래스입니다.
- Jetpack 라이브러리의 일부이며, 라이프사이클, ViewModel, Compose 등과 연동하기 편하게 만들어진 Activity 클래스입니다.
- Compose 앱에서는 **setContent {}**를 사용해서 UI를 설정할 수 있는데, 이 기능을 제공하는 게 바로 ComponentActivity입니다.
✅ Jetpack Compose란?
Jetpack Compose = Android용 선언형 UI 프레임워크
- 기존에는 UI를 XML로 작성하고 Activity/Fragment에서 조작했죠.
- Compose는 UI를 Kotlin 코드로 직접 작성하고, 상태(state)에 따라 자동으로 다시 그려줘요.
👉 Compose는 XML 필요 없음, 모두 Kotlin으로 작성!
선언형 UI | "무엇을 그릴지"만 선언하면 됩니다. 상태(state)가 바뀌면 UI도 자동으로 갱신돼요. |
Kotlin 기반 | 모든 UI 코드를 Kotlin 안에서 작성 가능 — XML과 Java를 왔다 갔다 안 해도 돼요. |
빠른 개발 | 프리뷰, 핫 리로드 등 덕분에 실시간으로 UI 결과를 볼 수 있어요. |
코드 간결성 | XML + Activity보다 훨씬 짧고 명확한 코드로 같은 기능 구현 가능 |
일관성 | Compose만 써도 UI, 테마, 레이아웃, 애니메이션 등 모두 처리 가능 |
@Composable 함수 | UI 구성 요소를 정의하는 함수. 화면에 그릴 수 있는 단위. |
setContent {} | Compose에서 UI를 시작하는 진입점. Activity에서 사용. |
State | UI가 반응하는 데이터. 값이 바뀌면 해당 UI가 자동 갱신됨. |
Modifier | 크기, 배경, 정렬 등 UI 속성을 꾸미는 데 사용됨. |
Preview | Android Studio에서 UI 미리보기를 가능하게 하는 도구. |
✅ UI란?
UI = 사용자와 프로그램이 상호작용하는 화면이나 요소
쉽게 말해서, 사람이 앱이나 웹사이트를 사용할 때 눈에 보이는 모든 것이 UI입니다.
버튼, 텍스트, 이미지, 메뉴, 스크롤, 입력창 같은 것들이 전부 UI예요.
📱 예를 들어 스마트폰 앱에서 UI는 이런 것들:
- 앱을 열었을 때 보이는 화면 디자인
- 버튼, 텍스트, 아이콘
- 입력창, 리스트, 사진
- 하단 탭 메뉴, 슬라이드 메뉴
- 화면 전환 효과, 애니메이션 등
✅ ComponentActivity 쉽게 말하면?
**"화면을 보여주는 기본 틀"**이에요.
앱을 실행했을 때 화면이 하나 뜨잖아요? 그 화면을 만드는 데 필요한 기본 뼈대가 바로 ComponentActivity예요.
📱 예를 들어볼게요
앱을 만든다고 상상해보세요.
화면에 "안녕하세요"라고 글자를 띄우고 싶어요.
그럼 이런 구조가 필요해요:
- 앱을 실행하면 →
- 화면이 켜지고 →
- 거기다 글자를 보여줘야 해요.
이때, 그 **화면 자체를 담당하는 게 ComponentActivity**예요.
🧱 실제 코드 예시 (초보용)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text("안녕하세요!") // 화면에 보일 글자
}
}
}
- MainActivity는 우리 앱의 첫 화면.
- ComponentActivity를 상속(물려받아서) 화면을 만들 수 있게 해요.
- setContent {} 안에서 Compose로 UI를 그립니다.
🧠 아주 쉽게 기억하기
Activity | 화면 하나하나를 나타냄 (앱에 보이는 창) |
ComponentActivity | Jetpack Compose용 화면을 만들기 위한 기본 클래스 |
MainActivity | 앱 실행 시 처음 보여주는 화면. 이게 보통 ComponentActivity를 사용해요. |
개념 역할
🎨 그림으로 비유하면...
ComponentActivity는 빈 액자 같아요.
그리고 setContent {}는 그 안에 그릴 그림이에요.
우리는 Compose를 써서 그림을 그려넣는 거죠!
✅ Override란?
override는 **"덮어쓰기" 또는 "재정의"**라는 뜻이에요.
프로그래밍에서는 부모 클래스(상위 클래스)에 있는 기능을 자식 클래스(내가 만든 클래스)에서 바꿔서 쓰고 싶을 때 사용해요.
누군가 이미 만든 기능이 있는데,
그걸 내가 다르게 동작하게 하고 싶을 때 override를 써요.
📱 예시로 설명 (Android 기준)
class MainActivity : ComponentActivity() {
// 앱이 처음 실행될 때 호출되는 함수
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 화면에 UI를 그리는 코드
setContent {
Text("Hello, Android!")
}
}
}
여기서 onCreate() 함수는 ComponentActivity가 원래 가지고 있는 기능이에요.
그런데 우리가 이 함수 안에 새로운 동작을 넣고 싶으니까 override를 사용해서 덮어쓴 거예요.
🧠 비유로 이해하기
- 엄마가 만든 **요리 레시피(부모 클래스 함수)**가 있어요.
- 그런데 나는 거기에 내 입맛대로 바꾸고 싶어요.
- 그래서 재료나 순서를 바꿔서 다시 만든다 = override!
🔧 override 없이 안 되나?
안 돼요.
이미 부모 클래스에 있는 함수를 바꿔 쓰려면,
Kotlin은 반드시 override 키워드를 써야 해요.
"나는 일부러 덮어쓰는 거예요!" 라고 명확하게 표시하는 거예요.
📌 정리
override | 부모가 만든 함수를 다시 정의해서 다르게 동작하게 만듦 |
왜 씀? | 앱이 실행될 때 하고 싶은 일을 내 맘대로 지정하려고 |
어디서 씀? | 보통 onCreate(), onStart() 같은 시스템 함수 덮어쓸 때 |
✅ fun
fun은 Kotlin에서 함수를 만들 때 쓰는 키워드예요.
쉽게 말해서, fun은 **"이건 어떤 동작을 하는 코드야!"**라고 선언하는 거예요.
**fun = function(함수)**의 줄임말이에요.
**"무언가 일을 시키는 코드 덩어리"**를 만들 때 써요.
📋 예시
fun sayHello() {
println("안녕하세요!")
}
이 코드는 sayHello()라는 이름의 함수를 만든 거예요.
이 함수는 호출하면 **"안녕하세요!"**를 출력해요.
sayHello() // 실행하면 콘솔에 안녕하세요! 출력
🧠 비유로 이해하기
- fun은 레시피 카드라고 생각하면 돼요.
- 레시피를 만들어 놓고, 필요할 때 꺼내서 쓰는 거예요.
- 즉, fun은 나중에 실행할 수 있는 "기능 묶음"을 만드는 방법이에요.
🔧 매개변수(parameter)와 반환(return)
함수는 입력값을 받아서, 결과값을 돌려줄 수도 있어요.
fun add(a: Int, b: Int): Int {
return a + b
}
- a, b는 함수에 주는 입력값
- Int는 반환되는 값의 타입 (정수)
- return은 결과를 돌려줌
val result = add(3, 5) // result는 8
📦 Compose에서의 fun
Compose에서는 이렇게 UI도 함수로 만들어요:
@Composable
fun Greeting(name: String) {
Text("Hello, $name!")
}
- Greeting이라는 **함수(UI 하나)**를 만든 거고
- 화면에 텍스트를 보여주는 역할을 해요.
✅ 정리
fun | Kotlin에서 함수를 정의할 때 사용하는 키워드 |
함수란? | 어떤 작업을 수행하는 코드 덩어리 |
사용 예 | 계산, UI 표시, 메시지 출력 등 |
형식 | fun 함수이름(입력): 반환타입 { 실행할 코드 } |
✅ onCreate란?
- Activity나 ComponentActivity 같은 화면 클래스에서
처음으로 실행되는 함수 - 앱 화면이 처음 만들어질 때 한 번만 실행돼요
- UI 구성, 초기 설정 등을 여기서 해요
📋 예시 (Compose 앱)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text("안녕하세요!") // 여기서 UI를 화면에 그립니다
}
}
}
하나씩 해석해볼게요:
override fun onCreate(...) | 기존의 onCreate를 덮어써서 내가 원하는 동작을 하게 만듦 |
super.onCreate(...) | 부모 클래스의 기본 설정도 실행하게 함 (필수!) |
setContent { ... } | 이 안에 Compose로 UI를 구성 |
코드 설명
🧠 비유로 쉽게
앱 = 연극
onCreate() = 무대 준비
앱을 처음 실행하면
👉 배우들(데이터) 준비
👉 무대 배경(UI) 설치
👉 관객 앞에 등장
이 모든 걸 onCreate()에서 해요!
📌 정리
언제 실행됨? | 앱이 시작될 때 (Activity가 처음 열릴 때) |
무슨 역할? | 화면을 구성하고 초기 설정을 담당 |
어디서 작성? | MainActivity 같은 Activity 안에 |
꼭 필요한가? | 네! 화면을 구성하려면 거의 필수예요 |
✅ savedInstanceState란?
앱 화면이 잠깐 사라졌다가 다시 돌아올 때,
원래 상태를 저장하고 복구할 수 있게 해주는 **상자(Bundle)**예요.
📦 예를 들어볼게요
앱을 사용하다가:
- 화면을 돌리거나(가로 ↔ 세로)
- 전화가 와서 앱이 잠깐 꺼지거나
- 앱이 메모리 부족으로 강제 종료됐다가 다시 열리면
그동안 입력한 값이나 상태가 그대로 남아있으면 좋겠죠?
그걸 저장하는 공간이 바로 savedInstanceState예요.
📋 예시 코드
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 상태가 저장돼 있다면 꺼내오기
val name = savedInstanceState?.getString("username")
setContent {
Text("안녕하세요, ${name ?: "게스트"}님")
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// 상태 저장하기
outState.putString("username", "홍길동")
}
🧠 쉽게 정리하면
savedInstanceState | 예전에 저장한 정보가 들어있는 상자 (읽기용) |
onSaveInstanceState() | 앱이 꺼지기 전에 상태를 저장하는 곳 (쓰기용) |
사용 예 | 입력창 내용, 점수, 선택된 탭 등 일시적인 상태 복구 |
용어 설명
📌 예를 들어 더 쉽게!
사용자가 이름 입력 → 화면 회전 | 이름이 사라지면 안 됨 |
onSaveInstanceState에서 이름 저장 | username = 홍길동 저장 |
onCreate(savedInstanceState)에서 복구 | 다시 앱이 열리면 "홍길동" 그대로 보여줌 |
상황 역할
❗ Jetpack Compose에서는?
Compose는 rememberSaveable() 같은 기능으로 이걸 더 쉽게 처리해줘요!
val name = rememberSaveable { mutableStateOf("") }
📌 요약
- savedInstanceState는 화면이 꺼졌다가 다시 켜질 때 데이터를 복원하는 데 쓰는 가방
- onSaveInstanceState()에서 값을 저장하고
- onCreate(savedInstanceState)에서 다시 꺼내 씁니다
✅ Bundle? vs Bundle 차이
Bundle | 절대로 null이 될 수 없음 |
Bundle? | null이 될 수도 있음 (값이 없을 수도 있음) |
표현 뜻
📋 예시
override fun onCreate(savedInstanceState: Bundle?) {
...
}
여기서 savedInstanceState는 Bundle?이죠.
즉, 값이 있을 수도 있고, 없을 수도 있다는 걸 의미해요.
왜냐하면:
- 앱이 처음 실행될 때는 저장된 상태가 없기 때문에 null
- 화면을 회전해서 다시 열릴 때는 이전 상태가 Bundle에 들어감
그래서 이렇게 사용하죠:
if (savedInstanceState != null) {
val name = savedInstanceState.getString("username")
}
🧠 쉽게 비유하면
Bundle | 무조건 가방 있음 | "빈 손이면 안 돼!" |
Bundle? | 가방이 있을 수도, 없을 수도 있음 | "가방 없으면 그냥 넘어가~" |
타입 예시 설명
💡 Kotlin에서는 안전하게 쓰기 위해 이렇게도 많이 씁니다
val name = savedInstanceState?.getString("username")
- ?.는 null이 아닐 때만 실행하고,
- null이면 그냥 무시하고 넘어가요.
📌 정리
Bundle | 반드시 존재해야 함 | null 체크 없이 바로 사용 가능 |
Bundle? | 없어도 괜찮음 | 앱 처음 실행 시에는 null일 수 있으므로 체크 필요 |
✅ 1. 상속 또는 구현(extends / implements)
클래스 선언이나 인터페이스 구현에서 사용
class MainActivity : ComponentActivity() {
// ...
}
의미:
- MainActivity는 ComponentActivity라는 **기능을 가진 클래스에서 확장(상속)**되었어요.
- 즉, ComponentActivity의 기능을 물려받았다는 뜻입니다.
Java의 extends와 같은 개념이에요.
📌 다른 예:
class MyFragment : Fragment()
class MyAdapter : RecyclerView.Adapter<MyViewHolder>()
✅ 2. 타입 지정 (Type annotation)
변수, 함수의 자료형을 지정할 때 사용
val name: String = "홍길동"
fun add(a: Int, b: Int): Int {
return a + b
}
의미:
- name은 String 타입
- a와 b는 Int 타입
- 함수의 반환값도 Int 타입
✅ 정리표
클래스 선언 | class A : B() | B 클래스를 상속받음 |
변수 타입 | val x: Int | x는 Int 타입 |
함수 파라미터 타입 | fun add(a: Int) | a는 Int 타입 |
반환 타입 | fun greet(): String | 결과가 String 타입 |
사용 위치 예시 의미
🧠 비유로 설명하면
- : 는 **"무언가의 종류를 설명하는 라벨"**이라고 생각하면 돼요.
예:
val age: Int = 20
👉 "age는 정수입니다!" 라고 알려주는 것
class Dog : Animal()
👉 "Dog는 Animal의 자식입니다!" 라고 알려주는
✅ 이 구문의 구조는?
savedInstanceState: Bundle?
이걸 해석하면:
- savedInstanceState → 변수 이름 (매개변수 이름)
- : → "이 변수는 어떤 타입이야"라고 알려주는 표시
- Bundle? → 이 변수의 자료형 (타입)
✅ 그래서 정확히 무슨 뜻이냐면:
"savedInstanceState라는 이름의 변수는 Bundle? 타입이다."
여기서 Bundle?은 **"Bundle일 수도 있고 null일 수도 있다"**는 뜻이에요.
✅ 전체 코드 속에서 보면:
override fun onCreate(savedInstanceState: Bundle?) {
// ...
}
- onCreate()는 앱이 실행되거나 재실행될 때 호출되는 함수
- 그 안의 savedInstanceState 매개변수는 앱의 이전 상태 정보가 담긴 객체예요
- 이 정보는 없을 수도 있으므로 Bundle?로 받는 거예요
📌 다시 정리
savedInstanceState | 매개변수 이름 (변수명) |
: | "이 변수는 어떤 타입이다" 라는 뜻 |
Bundle? | null이 가능한 Bundle 타입 |
부분 의미
🧠 비유로 말하면
이 가방은 Bundle? 타입이에요. 안에 뭔가 들어있을 수도 있고, 비어 있을 수도 있어요.
:는 **"이 가방은 어떤 종류다"**라고 이름표 붙여주는 역할입니다.
✅ 핵심 정리
✔️ savedInstanceState는 이름일 뿐
✔️ 타입(Bundle?)은 Kotlin 문법상 반드시 명시해야 합니다.
🔍 왜 타입을 써야 하나요?
Kotlin은 정적 타입 언어예요.
→ 즉, 모든 변수와 매개변수는 어떤 타입인지 컴파일러가 정확히 알아야 해요.
그래서 Android가 내부적으로 onCreate(savedInstanceState: Bundle?)를 정의할 때도:
open class ComponentActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
}
}
이렇게 반드시 타입을 명시합니다.
🧠 쉽게 비유하면
savedInstanceState는 "사람 이름"이고
Bundle?은 "그 사람이 어떤 직업을 가졌는지"를 말하는 거예요.
fun greet(person: String)
- person이라는 이름은 우리가 정할 수 있지만
- String 타입을 생략하면 컴퓨터가 뭘 받을지 몰라요
🎯 결론
savedInstanceState | 자주 쓰는 이름이지만 단순한 변수 이름 |
Bundle? | 이 변수의 정확한 자료형 |
타입 생략 가능? | ❌ Kotlin에서는 타입 생략하면 에러 (특히 오버라이드 메서드에서) |
왜 쓰나? | 컴파일러가 정확히 타입을 알아야 하기 때문 |
항목 설명
그리고 실제로 onCreate() 메서드를 직접 구현하는 개발자 입장에서는 이 매개변수가 꼭 어떤 타입인지 알아야, 안에 있는 값을 꺼낼 수 있어요. (getString(), getInt() 등)
savedInstanceState를 다른 타입으로 정의하면 오류가 납니다.
왜냐하면, 이 함수는 **Android 시스템이 호출하는 "약속된 형태의 함수"**이기 때문이에요.
✅ 예시: 다른 타입으로 바꾸면?
override fun onCreate(savedInstanceState: String) {
// ❌ 타입 오류!
}
결과는?
👉 컴파일 에러 또는 앱 실행 중 크래시(비정상 종료) 발생
왜냐하면 Android 시스템은 onCreate()를 호출할 때 Bundle? 타입의 값을 넣어주기로 약속돼 있어요.
✅ 이유: "오버라이딩 규칙 위반"
Kotlin (또는 Java)에서는 상속받은 함수를 재정의(override) 할 때,
**함수의 모양(시그니처)**이 원본과 완전히 똑같아야 합니다:
함수 이름 | onCreate |
매개변수 개수 | 1개 |
매개변수 타입 | Bundle? |
반환 타입 | Unit (즉, 반환값 없음) |
항목 같아야 함
📌 정리하면
savedInstanceState: Bundle? → savedInstanceState: String | ❌ 시스템이 onCreate()를 제대로 인식 못함 |
이름만 바꿈 (예: abc: Bundle?) | ❌ Android 시스템은 변수 이름에는 관심 없음, 타입만 중요함, 그러나 보통 관례적으로 이름도 맞춤 |
변경 결과
🔧 더 정확히 말하면...
override fun onCreate(myState: Bundle?) {
// ✅ 변수 이름만 바꾸는 건 가능
}
- 변수 이름은 바꿔도 괜찮아요 (컴파일됨)
- 하지만 타입을 바꾸면 onCreate()를 제대로 "오버라이드"하지 못해서 Android가 호출을 안 해요
✅ 결론 요약
savedInstanceState 이름 바꿔도 되나요? | ✅ 가능 (하지만 보통 그대로 둠) |
타입을 Bundle?이 아닌 다른 걸로 바꾸면? | ❌ Android 시스템이 호출하지 않음, 앱이 비정상 작동 |
이유는? | Android는 onCreate(Bundle?) 시그니처로 정의된 메서드를 찾아서 실행하기 때문 |
✅ Kotlin 기본 타입들
String | 글자(문자열) | "안녕하세요" |
Int | 정수 | 1, 100, -5 |
Double | 실수 (소수점 숫자) | 3.14, 99.99 |
Boolean | 참/거짓 | true, false |
Char | 한 글자 | 'A', '가' |
Long | 아주 큰 정수 | 1234567890L |
Float | 소수점 숫자 (작은 정확도) | 3.14f |
타입 설명 예시
⚠️ Double과 Float 차이: Float은 소수점 정밀도가 낮아요 (f 붙여야 함)
✅ Kotlin 컬렉션 타입들
List<T> | 순서 있는 목록 (읽기 전용) | listOf("사과", "배") |
MutableList<T> | 순서 있는 목록 (수정 가능) | mutableListOf(1, 2, 3) |
Set<T> | 중복 없는 집합 | setOf("A", "B", "A") → A, B |
Map<K, V> | 키-값 쌍 저장 | mapOf("이름" to "홍길동") |
타입 설명 예시
T, K, V는 타입 자리에 들어가는 **제너릭(일반화된 타입)**이에요.
✅ Android에서 자주 쓰는 타입들
Bundle | 여러 데이터를 저장하는 묶음 | bundle.putString("name", "홍길동") |
Intent | 화면 이동, 데이터 전달용 객체 | Intent(this, NextActivity::class.java) |
Context | 앱 정보, 리소스 접근용 | this, applicationContext |
View | 화면에 보이는 요소 | 버튼, 텍스트, 이미지 등 |
Activity | 하나의 화면(화면 단위 클래스) | MainActivity, LoginActivity |
Fragment | 화면 일부 조각 (Activity보다 작음) | MyFragment |
타입 설명 예시
✅ null 허용 타입 (?)
Kotlin에서는 null을 허용하는지 여부를 타입에 표시해야 해요.
String | null 불가능 | val name: String = "홍길동" |
String? | null 가능 | val name: String? = null |
타입 설명 예시
이걸 nullable 타입이라고 해요.
✅ 사용자 정의 타입 (클래스)
자신만의 타입도 만들 수 있어요:
data class User(val name: String, val age: Int)
val user: User = User("홍길동", 25)
→ User도 타입이 된 거예요.
✅ 요약표
기본 타입 | String, Int, Boolean, Double |
컬렉션 타입 | List, Map, Set |
Android 타입 | Bundle, Intent, Activity, View |
nullable 타입 | String?, Int? 등 |
사용자 정의 | class, data class 등으로 만든 타입 |
범주 예시 타입
좋은 질문이에요!
onCreate()는 Android에서 시스템이 자동으로 호출하는 생명주기 함수이기 때문에,
다른 이름으로 바꾸거나 다른 함수를 써도 자동으로 실행되지 않습니다.
✅ 기본 구조 (정상 작동하는 예)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
println("앱이 시작되었어요!")
}
- onCreate()는 Activity가 처음 생성될 때 자동으로 실행돼요
- Android가 이 함수를 호출해주는 거예요
❌ 다른 함수명을 쓰면?
예를 들어 이렇게 쓰면:
override fun startApp(savedInstanceState: Bundle?) {
println("앱을 시작합니다!")
}
결과:
- Android 시스템은 startApp()을 모름 → 실행되지 않음
- 아무리 println()을 넣어도 화면에 아무 일도 안 생김
❌ 다른 매개변수를 쓰면?
override fun onCreate(someText: String) {
println("앱 시작: $someText")
}
결과:
- 에러 발생 ❌
- 'onCreate' overrides nothing
왜냐하면, 시스템은 onCreate(Bundle?) 형태로 정의된 함수만 호출하기 때문이에요.
✅ 왜 이게 중요한가?
Android는 Activity의 생명주기를 자동으로 관리합니다.
시스템이 onCreate() → onStart() → onResume() 등의 정해진 메서드를 호출하죠.
이걸 Activity 생명주기 (Lifecycle) 라고 해요.
따라서 onCreate()를 안 쓰거나 잘못 쓰면 앱이 제대로 시작되지 않습니다.
🎯 요약
함수 이름 | ❌ 자동 호출 안 됨 |
매개변수 타입 변경 | ❌ 에러 발생, 오버라이드 실패 |
함수 이름도 틀리고 매개변수도 틀림 | ❌ 시스템이 무시함 |
바꾼 내용 결과
✅ 결론
- onCreate()는 Android가 자동으로 호출해주는 필수 함수
- 다른 이름이나 모양으로 바꾸면 절대 실행되지 않음
- 꼭 이렇게 써야 해요:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 초기화 코드
}
좋아요! onCreate(savedInstanceState: Bundle?)를 초보자도 알 수 있게 하나하나 쉽게 해석해볼게요 😊
🔍 전체 구조:
override fun onCreate(savedInstanceState: Bundle?) {
// 앱 시작 시 실행되는 코드
}
✅ 한 줄씩 해석
override | 상속받은 메서드를 재정의한다는 뜻 (Android 시스템에서 제공하는 기본 함수를 덮어씀) |
fun | Kotlin에서 함수를 만든다는 키워드 |
onCreate | Android 시스템이 앱이 시작될 때 자동으로 호출하는 함수 이름 |
savedInstanceState: | 함수의 매개변수 이름 (이전 상태를 담고 있는 변수) |
Bundle? | 매개변수의 타입 → 번들이라는 자료형이며, null이 될 수도 있다는 뜻 |
{ ... } | 이 안에 있는 코드가 앱 시작 시 실행됨 |
부분 뜻
🧠 풀어 쓰면 이렇게 돼요:
"나는 Android가 호출하는 onCreate()라는 함수를 재정의할 거야.
이 함수는 앱이 처음 시작될 때 실행돼.
만약 이전에 저장된 상태가 있다면, savedInstanceState라는 번들에 들어와.
그걸 활용해서 앱의 상태를 복구할 수도 있어."
✅ 예를 들어 설명하면
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // 부모가 하는 기본 동작도 같이 해줘
println("앱이 시작됨!") // 내가 추가한 동작
}
실행되면:
앱이 시작됨!
이렇게 출력되고, 앱 화면도 이 시점에 준비됩니다.
📌 결론 요약
override | "이 함수는 시스템 거를 다시 쓴 거예요!" |
fun | "지금부터 함수를 정의할게요" |
onCreate | "앱이 처음 켜질 때 실행되는 함수 이름" |
savedInstanceState: Bundle? | "예전에 저장해둔 데이터가 여기 들어올 수도 있어요 (없을 수도 있어요)" |
{ ... } | "이 안의 코드가 실제로 실행돼요" |
요소 쉽게 설명하면
✅ super의 기본 개념
super는 부모 클래스의 메서드나 프로퍼티(속성)을 자식 클래스에서 호출할 때 사용합니다.
예를 들어, 클래스 상속을 사용하고 있을 때, 부모 클래스의 기능을 자식 클래스에서 그대로 사용하거나 오버라이드할 수 있어요.
📌 예시: 부모와 자식 클래스
open class Animal {
open fun makeSound() {
println("동물이 소리를 낸다!")
}
}
class Dog : Animal() {
override fun makeSound() {
super.makeSound() // 부모 클래스의 makeSound()를 먼저 호출
println("멍멍!")
}
}
👀 코드 설명:
- Animal 클래스는 makeSound()라는 기본 메서드를 가지고 있어요.
- Dog 클래스는 Animal 클래스를 상속받아, makeSound()를 오버라이드합니다.
- **super.makeSound()**는 **부모 클래스의 makeSound()**를 먼저 호출해서 기본 동작을 실행하고, 그 다음에 자식 클래스에서 추가한 **멍멍!**을 출력합니다.
🧩 코드 실행 결과:
val dog = Dog()
dog.makeSound() // 부모 메서드 + 자식 메서드 출력
출력:
동물이 소리를 낸다!
멍멍!
✅ super는 언제 쓰는 걸까?
- 부모 클래스의 메서드나 프로퍼티를 호출하고 싶을 때
- 부모 클래스에서 이미 구현된 기능을 자식 클래스에서 그대로 쓰고 싶을 때 사용합니다.
- 오버라이드한 메서드에서 부모의 기본 기능을 먼저 실행하고 싶을 때
- 예를 들어, 부모 클래스에서 미리 처리한 작업을 먼저 하고, 자식 클래스에서 추가 작업을 할 때 사용합니다.
✅ 더 자세한 예시
1. 부모 클래스에서 super 없이 그냥 호출
open class Animal {
open fun eat() {
println("음식을 먹는다")
}
}
class Dog : Animal() {
override fun eat() {
println("강아지가 음식을 먹는다")
}
}
출력:
강아지가 음식을 먹는다
여기서는 super.eat() 없이 자식 클래스에서만 eat()을 구현한 거예요.
2. 부모 클래스의 기능을 먼저 호출하고 나서 추가 작업
open class Animal {
open fun eat() {
println("음식을 먹는다")
}
}
class Dog : Animal() {
override fun eat() {
super.eat() // 부모의 eat() 호출
println("강아지가 음식을 먹는다")
}
}
출력:
음식을 먹는다
강아지가 음식을 먹는다
- **super.eat()**가 먼저 호출되어 **부모의 eat()**이 실행된 후, **자식 클래스의 eat()**이 실행된 거예요.
✅ 정리
super란? | 부모 클래스의 메서드나 프로퍼티를 자식 클래스에서 호출하는 키워드 |
언제 사용? | 부모 클래스에서 정의된 메서드를 자식 클래스에서 재사용하거나, 오버라이드할 때 |
예시 | super.makeSound() → 부모 클래스의 makeSound()를 호출 후, 자식 클래스에서 추가 작업 |
super.에서 .의 역할은?
마침표(.)는 **"멤버 접근 연산자"**입니다. 즉, 객체의 메서드나 프로퍼티(속성)에 접근할 때 사용해요.
super는 부모 클래스를 가리키는 키워드이고, 마침표는 부모 클래스의 메서드나 프로퍼티에 접근하는 역할을 합니다.
📌 간단한 예시
open class Animal {
fun makeSound() {
println("동물이 소리를 낸다!")
}
}
class Dog : Animal() {
fun bark() {
super.makeSound() // 부모 클래스의 makeSound() 호출
println("멍멍!")
}
}
🧩 코드 분석:
- super.makeSound()에서 super는 부모 클래스인 Animal을 가리키고 있어요.
- .(마침표)는 Animal 클래스 안의 makeSound() 메서드에 접근하는 연산자입니다.
- 즉, super.makeSound()는 부모 클래스의 makeSound() 메서드를 호출하는 것입니다.
마침표의 역할
- super → 부모 클래스(부모 객체) 자체를 가리킴
- . → 부모 클래스의 특정 멤버(메서드나 프로퍼티)에 접근하는 연산자
🔍 예시 코드
open class Animal {
open fun makeSound() {
println("동물이 소리를 낸다!")
}
open val type: String = "동물"
}
class Dog : Animal() {
override fun makeSound() {
super.makeSound() // 부모의 makeSound() 호출
println("멍멍!")
}
fun printType() {
println(super.type) // 부모의 type 프로퍼티를 호출
}
}
🧩 실행해보면:
val dog = Dog()
dog.makeSound() // 부모의 makeSound() + 자식의 makeSound()
dog.printType() // 부모의 type 출력
출력:
동물이 소리를 낸다!
멍멍!
동물
여기서 마침표(.)의 역할
- super.makeSound() → 부모 클래스 Animal의 makeSound()를 호출
- super.type → 부모 클래스 Animal의 type 프로퍼티를 호출
📌 정리
- super는 부모 클래스를 가리키는 키워드
- .(마침표)는 부모 클래스의 멤버(메서드/프로퍼티)에 접근하는 연산자
- super.makeSound()는 부모 클래스의 makeSound()를 호출하는 방식입니다.
정말 좋은 질문이에요!
super.onCreate(savedInstanceState)에서는 왜 savedInstanceState: Bundle?처럼 타입을 다시 적지 않아도 되는지 궁금하신 거죠?
초보자도 이해할 수 있도록 간단하고 쉽게 설명드릴게요 😊
🔧 먼저 결론부터!
super.onCreate(savedInstanceState)에서는
이미 타입이 정해져 있기 때문에, 다시 쓸 필요가 없어요!
🧩 예시 코드
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) // 여기선 타입을 안 씀
}
여기서 savedInstanceState: Bundle?은 함수 선언부에서만 타입을 지정합니다.
함수 안에서 savedInstanceState를 사용할 때는 이미 어떤 타입인지 알기 때문에
다시 : Bundle? 같은 타입을 쓸 필요가 없는 거예요.
📘 쉽게 비유하면:
📦 "택배 상자" 비유
val box: String = "택배 상자"
println(box)
- 처음 변수 box를 만들 땐 "이건 문자열이야"라고 타입을 알려줘야 해요.
- 하지만 println(box) 할 때는 **이미 box가 문자열(String)**이라는 걸 컴퓨터가 알아요.
- 그러니까 다시 println(box: String)이라고 할 필요가 없죠!
🧠 조금 더 정확하게 말하면:
- super.onCreate(...)는 부모 클래스의 onCreate 함수를 호출하는 거예요.
- 그 함수는 이미 정의되어 있고, 매개변수의 타입도 정해져 있어요.
- 그래서 우리는 변수 이름만 넘기면 되고, 다시 타입을 쓸 필요가 없습니다.
✅ 정리
함수 정의할 때 | ✅ 반드시 써야 함 | 컴파일러가 이게 무슨 타입인지 알아야 하니까 |
함수 안에서 쓸 때 | ❌ 쓸 필요 없음 | 이미 타입이 정해져 있으니까 |
위치 타입 써야 하나요? 이유
좋은 질문이에요!
enableEdgeToEdge()는 Android에서 화면을 "모서리 끝까지" 넓게 사용하게 해주는 함수입니다.
초보자도 이해하기 쉽게 하나씩 해석해볼게요 😊
🧾 함수 이름 해석
enable | 사용 가능하게 만든다 (켜다, 활성화하다) |
EdgeToEdge | 화면의 가장자리(edge)부터 가장자리까지 모두 사용하겠다 |
부분 뜻
➡️ 즉, enableEdgeToEdge()는 **"화면을 끝에서 끝까지 확장해서 사용하도록 설정한다"**는 의미예요.
📱 왜 쓰는 걸까?
보통 앱은 **상태 표시줄(시간, 배터리 등)**이나 하단 네비게이션 바 때문에
화면 위아래 여백이 생깁니다.
enableEdgeToEdge()를 사용하면:
- 이 여백을 없애고,
- 앱의 콘텐츠(텍스트, 이미지 등)를 전체 화면에 표시할 수 있게 해줘요.
📌 예: 쓰는 위치
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
// 화면 구성
}
}
- onCreate() 안에서 enableEdgeToEdge()를 부르면,
- 앱이 시작될 때 화면 전체를 넓게 쓰겠다고 설정하는 거예요.
🎨 결과 모습
- 콘텐츠가 상단 상태 표시줄 아래까지 내려오고,
- 하단 내비게이션 바 뒤쪽까지 확장됩니다.
- 단, 그 영역은 투명하게 처리하거나 적절한 패딩을 줘서 보기 좋게 만들어야 해요.
✅ 정리
목적 | 앱 화면을 모서리부터 모서리까지 꽉 차게 보여주기 위해 |
쓰는 곳 | onCreate() 안에서 |
효과 | 상태바/내비게이션 바 밑까지 화면 사용 가능 |
필요 이유 | 요즘 스마트폰은 전면 화면 비율이 크기 때문에 이런 설정이 필요해요 |
항목 설명
좋은 질문이에요!
GreetingCardTheme은 앱의 색상, 글꼴, 배경 같은 "디자인 스타일(테마)"을 정해주는 함수예요.
이건 Jetpack Compose에서 앱 전체의 UI 스타일을 통일하기 위해 사용합니다.
🎨 간단히 말하면:
GreetingCardTheme은 앱의 외형을 정해주는 디자인 틀이에요.
배경색, 글자색, 버튼 모양 등 전체적인 느낌을 조절할 수 있어요.
📌 코드 예시
setContent {
GreetingCardTheme {
// 여기에 들어가는 모든 UI는 이 테마를 따라요
Surface(modifier = Modifier.fillMaxSize()) {
Greeting("홍길동")
}
}
}
이렇게 되면:
- Greeting("홍길동")은 GreetingCardTheme이 정해준 색상, 폰트 등을 자동으로 따라갑니다.
🔧 GreetingCardTheme은 어디서 정의될까?
보통 ui/theme/Theme.kt 파일에 정의돼 있어요.
안에 보면 MaterialTheme()을 감싸는 식으로 돼 있을 거예요.
@Composable
fun GreetingCardTheme(content: @Composable () -> Unit) {
MaterialTheme(
colorScheme = ..., // 색상
typography = ..., // 글꼴 스타일
shapes = ..., // 버튼 모양, 카드 둥글기 등
content = content
)
}
📌 여기서 하는 일:
- 색상 세트: 라이트 모드 / 다크 모드 색상 지정
- 글꼴: 앱에 쓰이는 기본 글꼴 크기, 굵기 설정
- 모양: 버튼이나 카드에 들어가는 둥근 모서리 등
💡 왜 필요한가요?
- 앱 전체 디자인을 일관되게 유지하려면 테마가 필요해요.
- 예를 들어 배경색, 텍스트 크기 등을 하나하나 지정하는 대신,
테마를 한 번에 적용하면 모든 화면에 똑같은 스타일이 적용돼서 편리해요.
✅ 정리
GreetingCardTheme | 앱 전체의 디자인 스타일을 정하는 함수 |
내부 구성 | 색상, 폰트, 모양 등을 MaterialTheme()을 통해 설정 |
사용하는 이유 | 앱 전체에 일관된 UI 디자인을 적용하려고 |
용어 설명
아주 좋은 질문이에요! 😄
Jetpack Compose에서 **Scaffold(스캐폴드)**는 자주 등장하는 UI 구성 요소인데요,
쉽게 말해서:
앱 화면의 기본 틀(뼈대, 구조)을 만들어주는 컨테이너예요.
🧱 Scaffold는 무엇인가요?
Scaffold는 영어로 비계라는 뜻인데,
건축할 때 건물을 지탱하는 틀처럼,
Compose UI에서도 앱의 화면 레이아웃을 정리해주는 틀 역할을 해요.
📐 예를 들어 Scaffold는 다음을 자동으로 배치해줘요:
topBar | 상단 앱바 (예: 제목, 메뉴 아이콘 등) |
bottomBar | 하단 내비게이션 바 |
floatingActionButton | 둥둥 떠 있는 + 버튼 |
drawerContent | 왼쪽에서 열리는 사이드 메뉴 |
content | 화면의 주요 내용 영역 |
요소 설명
📌 기본 사용 예시
Scaffold(
topBar = {
TopAppBar(title = { Text("My App") })
},
floatingActionButton = {
FloatingActionButton(onClick = { /* 클릭 처리 */ }) {
Icon(Icons.Default.Add, contentDescription = "추가")
}
},
content = { padding ->
// 화면의 실제 내용
Text("Hello World!", modifier = Modifier.padding(padding))
}
)
🎯 왜 써야 할까?
- 상단바, 하단바, 플로팅 버튼 같은 공통 UI 요소를 쉽게 배치할 수 있어요.
- 화면의 레이아웃 구조를 명확하게 만들고, 중복 코드도 줄여줍니다.
- content 안의 요소들은 padding을 받아서,
상단바나 하단바에 겹치지 않도록 자동으로 여백이 조절돼요.
✅ 정리
Scaffold | 화면을 구성하는 기본 틀 (상단바, 하단바, 본문 등) |
장점 | 일관된 구조, 자동 여백 처리, UI 코드 정돈 |
자주 쓰는 곳 | 앱 화면 전체를 구성할 때 거의 필수적으로 사용 |
용어 의미
좋은 질문이에요!
Jetpack Compose에서 자주 보이는 **modifier**는 UI 요소(컴포넌트)에 모양이나 동작을 추가해주는 설정 도구예요.
쉽게 말해서:
modifier는 "어떻게 보여줄지"를 결정하는 꾸미기 도구입니다.
🎨 예를 들어볼게요:
Text("Hello", modifier = Modifier.padding(16.dp))
위 코드에서:
- Text("Hello")는 글자를 보여주는 컴포넌트
- modifier = Modifier.padding(16.dp)는 **이 글자에 16dp 여백(padding)**을 준 거예요
📦 Modifier로 할 수 있는 일들
여백 주기 | Modifier.padding(16.dp) |
크기 정하기 | Modifier.size(100.dp) |
색상 배경 | Modifier.background(Color.Red) |
클릭 이벤트 | Modifier.clickable { /* ... */ } |
정렬 | Modifier.align(...), Modifier.fillMaxWidth() |
그림자 효과 | Modifier.shadow(4.dp) |
기능 예시
🧱 여러 Modifier를 연결할 수도 있어요
Modifier
.padding(8.dp)
.background(Color.Gray)
.size(200.dp)
이렇게 연결하면:
- 여백을 주고
- 배경색을 입히고
- 크기를 지정해요
➡️ 마치 레고 블럭처럼 꾸미기 기능을 붙여 나가는 방식이에요.
🔁 왜 따로 modifier를 쓰는 걸까?
- UI와 스타일을 깔끔하게 분리할 수 있어요.
- 여러 컴포넌트에 재사용하기 쉬움
- **체이닝(연결)**해서 자유롭게 조합 가능
✅ 정리
modifier | Compose UI에 크기, 배경, 여백, 동작 등을 추가하는 도구 |
문법 | modifier = Modifier.padding(...).background(...) |
장점 | 깔끔한 코드, 유연한 UI 꾸미기 가능 |
개념 설명
좋아요! Modifier.fillMaxSize()는 컴포넌트를 화면(부모 공간)의 최대 크기까지 늘려주는 Modifier예요.
아주 자주 쓰이고, 굉장히 중요한 역할을 해요.
초보자도 이해할 수 있도록 쉽게 설명해볼게요 😊
📐 해석
Modifier.fillMaxSize() | 부모가 허용하는 최대 너비 + 최대 높이로 꽉 채워라! |
코드 뜻
즉, 이 컴포넌트는 **"남는 공간이 있으면 다 써라"**는 의미예요.
🧱 예시
Box(modifier = Modifier.fillMaxSize()) {
// 이 Box는 화면 전체를 채움
}
이렇게 하면 Box가 화면 전체를 꽉 채우게 돼요.
그 안에 들어가는 다른 컴포넌트도 이 공간 안에서 배치되죠.
📦 비교 예시
// 화면의 100% 사용
Modifier.fillMaxSize()
// 화면 너비만 100%, 높이는 내용에 맞게
Modifier.fillMaxWidth()
// 화면 높이만 100%, 너비는 내용에 맞게
Modifier.fillMaxHeight()
// 화면의 50%만 사용
Modifier.fillMaxSize(0.5f) // 0.0 ~ 1.0 비율
📱 실제 사용 예
Surface(
modifier = Modifier.fillMaxSize(), // 화면을 꽉 채우고
color = MaterialTheme.colorScheme.background
) {
Greeting("홍길동") // 이 안에서 UI를 배치
}
이 코드는 앱 화면을 배경색으로 가득 채우고, 그 위에 Greeting()을 올려놓는 구조예요.
✅ 정리
역할 | 화면(또는 부모 컴포넌트)의 크기만큼 컴포넌트를 키움 |
사용 시기 | 배경을 채울 때, 전체 레이아웃을 만들 때 |
자주 쓰는 곳 | Surface, Box, Column, Scaffold 등 |
항목 설명
좋은 질문이에요! Jetpack Compose에서 자주 보게 되는 Greeting은 보통 직접 만든 함수형 UI 컴포넌트입니다.
쉽게 말하면:
Greeting()은 화면에 "Hello" 같은 텍스트를 보여주는 함수예요.
앱을 처음 만들 때 자동 생성되는 예시용 UI 함수죠.
📌 예시 코드
보통 아래처럼 정의되어 있어요:
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
여기서 하는 일:
- Greeting("홍길동") → 화면에 Hello 홍길동! 텍스트를 보여줌
- name 매개변수로 사용자 이름 같은 걸 받아서 보여줄 수 있음
- Text()는 Compose에서 글자를 보여주는 기본 함수예요
🧩 어디서 호출될까?
보통 setContent 안이나 Preview 함수 안에서 사용돼요:
setContent {
Greeting("홍길동")
}
또는:
@Preview
@Composable
fun GreetingPreview() {
Greeting("Android")
}
이렇게 하면 미리보기에서도 Hello Android! 라는 글자를 볼 수 있어요.
✅ 정리
Greeting | 직접 만든 Composable 함수 (예시 UI) |
역할 | name 값을 받아서 "Hello ○○" 텍스트를 보여줌 |
사용 위치 | setContent { }나 @Preview 안 |
왜 쓰나 | Compose의 함수형 UI 구조를 연습하고 미리보기 할 수 있도록 도와줌 |
항목 설명
아주 좋은 질문이에요! innerPadding은 주로 **Scaffold 안에서 사용하는 여백(padding)**을 뜻해요.
쉽게 말하면:
innerPadding은 상단바, 하단바 같은 UI 요소에 안 겹치도록 내용을 안쪽으로 밀어주는 여백이에요.
📌 예시 코드
Scaffold(
topBar = { TopAppBar(title = { Text("제목") }) },
content = { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) {
Text("안녕하세요")
}
}
)
여기서 중요한 부분은:
- content = { innerPadding -> ... }
- 이 innerPadding은 Scaffold가 자동으로 계산한 여백 값
- 상단바나 하단바가 화면을 가리지 않도록 적절한 padding 값을 content에 넘겨줍니다
📐 왜 필요한가요?
만약 innerPadding을 쓰지 않으면 이렇게 돼요:
- 글자나 버튼이 상단바 아래에 겹쳐져서 보이거나,
- 하단 내비게이션 바에 가려질 수 있어요
→ 그래서 Modifier.padding(innerPadding)으로 여백을 주는 게 중요합니다.
✅ 정리
innerPadding | Scaffold가 계산해주는 상단/하단 UI 피하기용 여백 |
역할 | 앱 바, 내비게이션 바 등에 내용이 겹치지 않도록 여백을 줌 |
사용 위치 | Scaffold의 content 람다 함수 안에서 사용 |
항목 설명
좋은 질문이에요!
->는 Kotlin에서 **람다식(lambda expression)**을 쓸 때 사용하는 기호예요.
쉽게 말해서:
->는 "이 값을 받아서 이렇게 처리할게요" 라는 뜻이에요.
👀 예시로 보기
content = { innerPadding ->
Text("안녕하세요", modifier = Modifier.padding(innerPadding))
}
이건 이렇게 해석할 수 있어요:
- { innerPadding -> ... }
→ "innerPadding이라는 값을 받아서 중괄호 {} 안에 있는 코드를 실행하겠다" - 이건 사실 아래와 같은 함수를 짧게 줄인 형태예요:
fun content(innerPadding: PaddingValues) {
Text(modifier = Modifier.padding(innerPadding))
}
🧠 한 줄 요약
-> | 람다식에서 "입력 → 실행 내용" 을 구분하는 기호 |
예 | { x -> x * 2 } 는 "x를 받아서 2배로 반환해라" |
기호 뜻
✅ 정리
innerPadding -> | innerPadding 값을 받아서 뒤에 있는 UI 코드에 넘겨줌 |
-> 기호 | 람다식에서 입력과 실행을 구분할 때 사용 |
용어 설명
좋은 질문이에요! 이 코드를 보면 Scaffold 내부에서 **innerPadding**을 사용하지 않고 Greeting 컴포넌트에 직접 여백을 주려고 하는 모습이에요.
코드를 분석해보겠습니다:
Scaffold(modifier = Modifier.fillMaxSize()) {
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
1. Scaffold(modifier = Modifier.fillMaxSize())
- **Scaffold**는 화면의 기본 레이아웃을 설정해주는 컴포넌트이고, **modifier = Modifier.fillMaxSize()**는 Scaffold가 화면 크기 전체를 차지하도록 만듭니다.
2. { } 안의 코드:
- { } 안은 Scaffold의 content 람다 함수입니다. Scaffold는 이 함수의 인자로 **innerPadding**을 넘겨줍니다.
- 이 부분이 문제인 것 같아요. innerPadding을 받지 않으면 컴파일 에러가 발생할 거예요.
🚨 문제점:
innerPadding은 Scaffold가 계산한 여백인데, 그 값을 사용하려면 반드시 람다 인자에서 받아야 합니다.
innerPadding을 사용하려면 람다 인자에서 받아야 해요:
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding) // 여백 적용
)
}
하지만, innerPadding을 직접 사용하려면 반드시 innerPadding ->를 써야 합니다.
그렇다면 innerPadding을 쓰지 않으면 어떻게 되나요?
- innerPadding을 content 람다에서 받지 않으면 Greeting에서 사용할 수 없게 되므로, 코드가 컴파일되지 않아요.
- **여백을 자동으로 계산하는 innerPadding**이 없기 때문에, 화면 상단바와 하단바에 겹칠 가능성이 커지게 됩니다.
👀 예시:
올바른 코드:
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding) // 여백 적용
)
}
잘못된 코드 (innerPadding을 직접 사용하려면 컴파일 오류):
Scaffold(modifier = Modifier.fillMaxSize()) {
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding) // error: innerPadding을 찾을 수 없음
)
}
결론:
Scaffold에서 innerPadding을 받지 않으면 Greeting 컴포넌트에 여백을 적용할 수 없습니다. 컴파일 오류가 발생할 거예요.
따라서 **innerPadding ->**을 람다 인자에서 받아야 정상적으로 작동하고, 상단바와 하단바에 겹치지 않게 배치할 수 있습니다.
"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."
2. 한 줄 해석
전체 코드 구조
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
GreetingCardTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
1. class MainActivity : ComponentActivity() {
- MainActivity: 앱의 주요 액티비티 클래스.
- ComponentActivity(): ComponentActivity를 상속받고 있어요. ComponentActivity는 Compose 기반으로 UI를 구성할 때 기본적으로 사용하는 클래스입니다.
이 클래스는 앱의 주요 화면을 구현하는 부분이기 때문에, 화면을 표시하고, UI 이벤트를 처리하는 중요한 역할을 합니다.
2. override fun onCreate(savedInstanceState: Bundle?) {
- onCreate(): 액티비티가 처음 생성될 때 호출되는 함수. 액티비티의 초기화 작업을 이곳에서 합니다.
- savedInstanceState: Bundle?: 앱이 종료되거나 화면 회전 시 이전 상태를 복구할 수 있도록 돕는 인자입니다.
3. super.onCreate(savedInstanceState)
- super.onCreate(savedInstanceState): 부모 클래스인 ComponentActivity의 onCreate()를 호출해서 기본적인 초기화 작업을 실행해요.
- 이걸 호출하지 않으면 앱의 기본 동작이 정상적으로 이루어지지 않을 수 있습니다.
4. enableEdgeToEdge()
- enableEdgeToEdge(): 화면의 상단 상태 표시줄이나 하단 내비게이션 바와 겹치지 않게, UI를 화면 가장자리까지 확장하는 함수입니다.
이를 사용하면 앱이 화면의 전체 영역을 활용하도록 도와줍니다.
5. setContent { ... }
- setContent { ... }: Compose의 UI를 정의하는 곳입니다. setContent 안에 들어가는 내용은 앱 화면에 표시될 UI 컴포넌트입니다.
이 부분에서 Compose의 UI 요소들을 배치하게 됩니다.
6. GreetingCardTheme { ... }
- GreetingCardTheme: 앱의 전체적인 디자인(테마)을 설정하는 함수입니다. 예를 들어 배경색, 글꼴 크기, 버튼 스타일 등을 설정할 수 있어요.
7. Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> ... }
- Scaffold: 앱의 기본 레이아웃 구조를 만드는 함수입니다.
- 상단에 상단바(top bar), 하단에 내비게이션 바(bottom bar) 등을 쉽게 배치할 수 있도록 도와줍니다.
- modifier = Modifier.fillMaxSize()는 Scaffold가 화면 크기 전체를 채우게 만듭니다.
innerPadding은 Scaffold가 자동으로 계산한 여백으로, 이 값을 사용해 다른 컴포넌트가 상단바나 하단바에 겹치지 않게 배치할 수 있습니다.
8. Greeting(name = "Android", modifier = Modifier.padding(innerPadding))
- Greeting(name = "Android"): 앞에서 정의한 Greeting 컴포넌트를 호출합니다. name에 "Android"를 전달하여, 화면에 "Hello Android!" 라는 텍스트를 보여줍니다.
- modifier = Modifier.padding(innerPadding): innerPadding을 통해 상단바와 하단바에 겹치지 않도록 여백을 줍니다.
전체 요약:
- **MainActivity**는 앱의 주요 액티비티로, onCreate()에서 UI를 설정합니다.
- super.onCreate(savedInstanceState)로 기본 초기화 작업을 진행하고, enableEdgeToEdge()로 UI를 화면 가장자리까지 확장합니다.
- setContent { ... } 내에서 Compose UI를 정의합니다.
- GreetingCardTheme을 사용하여 앱의 디자인 테마를 설정합니다.
- Scaffold는 화면의 기본 구조를 제공하고, 그 안에 Greeting 컴포넌트를 배치하여 **"Hello Android!"**라는 메시지를 화면에 보여줍니다.
✅ 한 줄 요약:
이 코드는 **MainActivity**에서 화면 전체를 채우는 Scaffold 안에 **"Hello Android!"**라는 텍스트를 표시하며, 상단바와 하단바에 겹치지 않도록 적절한 여백을 추가하는 구조입니다.
이제 이해가 되셨나요? 더 궁금한 부분이 있으면 언제든지 질문해 주세요! 😊
'App Dev > Android Studio' 카테고리의 다른 글
Codelab 시작하기2 (0) | 2025.05.06 |
---|---|
Android Studio 초기설정2 (2) | 2025.04.27 |
Android Studio 초기설정1 (2) | 2025.04.26 |
Android Studio MeerKat 설치 (0) | 2025.04.26 |