Android ๊ฐ๋ฐ์ ์ฒ์ ์์ํ๋ ๋ถ๋ค์ ์ํ ์์ธํ ๊ฐ์ด๋์ ๋๋ค.
- ํ๋ก์ ํธ ์๊ฐ
- ํ๋ก์ ํธ ํด๋ ๊ตฌ์กฐ
- ์ฝํ๋ฆฐ ๊ธฐ๋ณธ ์ง์
- Android ์ฑ ์คํ ์๋ฆฌ
- UI์ ์ํธ์์ฉํ๋ ๊ธฐ์
- ์ฝ๋ ์์ธ ๋ถ์
- ์ฑ ๋น๋ ๋ฐ ์คํ ๋ฐฉ๋ฒ
์ด ํ๋ก์ ํธ๋ Android ๋ฉํฐ ๋ชจ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ค. ์ค๋งํธํฐ๊ณผ ์๋์ฐจ(Android Automotive) ๋ชจ๋์์ ์๋ํ๋ ์์ ํ๋ ์ด์ด ์ฑ์ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ๋ด๊ณ ์์ต๋๋ค.
- ์ธ์ด: Kotlin (์ฝํ๋ฆฐ) 2.0.21
- ์ต์ SDK: Android 9.0 (API 28)
- ๋์ SDK: Android 36
- ๋น๋ ๋๊ตฌ: Gradle 8.13
- UI ํ๋ ์์ํฌ: Material Design 3
- mobile: ์ค๋งํธํฐ์ฉ ์ฑ
- automotive: ์๋์ฐจ์ฉ ์ฑ
- shared: ๋ ์ฑ์ด ๊ณต์ ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
TsetApplication/
โโโ mobile/ # ๐ฑ ์ค๋งํธํฐ ์ฑ ๋ชจ๋
โโโ automotive/ # ๐ ์๋์ฐจ ์ฑ ๋ชจ๋
โโโ shared/ # ๐ฆ ๊ณต์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
โโโ gradle/ # โ๏ธ Gradle ๋น๋ ์ค์
โโโ build.gradle.kts # ๐ง ๋ฃจํธ ๋น๋ ์ค์
โโโ settings.gradle.kts # ๐ง ํ๋ก์ ํธ ์ค์
โโโ README.md # ๐ ์ด ๋ฌธ์
mobile/
โโโ build.gradle.kts # ๋ชจ๋ ๋น๋ ์ค์
โโโ src/
โ โโโ main/ # ๋ฉ์ธ ์์ค ์ฝ๋
โ โ โโโ java/com/example/tsetapplication/ # Kotlin ์ฝ๋
โ โ โ โโโ MainActivity.kt # ๋ฉ์ธ ํ๋ฉด ์กํฐ๋นํฐ
โ โ โโโ res/ # ๋ฆฌ์์ค ํ์ผ๋ค
โ โ โ โโโ layout/ # ํ๋ฉด ๋ ์ด์์
โ โ โ โ โโโ activity_main.xml # ๋ฉ์ธ ํ๋ฉด ๋ ์ด์์
โ โ โ โโโ drawable/ # ์ด๋ฏธ์ง, ๋ํ ๋ฑ
โ โ โ โโโ mipmap-*/ # ์ฑ ์์ด์ฝ (ํ์ง๋ณ)
โ โ โ โโโ values/ # ๊ฐ ๋ฆฌ์์ค
โ โ โ โ โโโ colors.xml # ์์ ์ ์
โ โ โ โ โโโ strings.xml # ๋ฌธ์์ด ์ ์
โ โ โ โ โโโ themes.xml # ํ
๋ง ์ ์
โ โ โ โโโ xml/ # ๊ธฐํ XML ์ค์
โ โ โโโ AndroidManifest.xml # ์ฑ ์ค์ ํ์ผ
โ โโโ test/ # ๋จ์ ํ
์คํธ
โ โโโ androidTest/ # UI ํ
์คํธ
โโโ proguard-rules.pro # ์ฝ๋ ๋๋
ํ ๊ท์น
- ์ฑ์ ์ค์ ๋ก์ง์ด ์์ฑ๋๋ ๊ณณ
- ํจํค์ง๋ช
ํ์:
com.example.tsetapplication - ๋ชจ๋
.ktํ์ผ(์ฝํ๋ฆฐ ํ์ผ)์ด ์ฌ๊ธฐ์ ์์น
- layout/: XML๋ก ์์ฑ๋ ํ๋ฉด ๋ ์ด์์
- drawable/: ์ด๋ฏธ์ง, ๋ฒกํฐ ๊ทธ๋ํฝ, ๋ํ ๋ฑ
- mipmap-*/: ์ฑ ์์ด์ฝ (hdpi, xhdpi ๋ฑ ํ์ง๋ณ ๋ถ๋ฅ)
- values/: ์์, ๋ฌธ์์ด, ์คํ์ผ ๋ฑ์ ๊ฐ ์ ์
- xml/: ๋ฐฑ์ ๊ท์น, ๊ธฐํ ์ค์ ํ์ผ
- ์ฑ์ ๊ธฐ๋ณธ ์ ๋ณด๋ฅผ ์ ์ธํ๋ ํ์ ํ์ผ
- ์ฑ ์ด๋ฆ, ์์ด์ฝ, ๊ถํ, ์กํฐ๋นํฐ ๋ฑ์ ์ ์
shared/
โโโ build.gradle # ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋น๋ ์ค์
โโโ src/
โ โโโ main/
โ โโโ java/com/example/tsetapplication/shared/
โ โ โโโ MyMusicService.kt # ์์
์๋น์ค
โ โโโ res/
โ โ โโโ xml/
โ โ โโโ automotive_app_desc.xml # Automotive ์ค์
โ โโโ AndroidManifest.xml # ์๋น์ค ์ ์ธ
- ์ ์ฒด ํ๋ก์ ํธ์ ๊ณตํต ํ๋ฌ๊ทธ์ธ ์ ์
- ๋ชจ๋ ๋ชจ๋์ ์ ์ฉ๋๋ ๊ธฐ๋ณธ ์ค์
- ํ๋ก์ ํธ์ ํฌํจ๋ ๋ชจ๋ ์ ์ธ
- ์ ์ฅ์(Repository) ์ค์
- Gradle ๋น๋ ์ฑ๋ฅ ์ค์
- JVM ๋ฉ๋ชจ๋ฆฌ ์ต์ ๋ฑ
- ์์กด์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฒ์ ๊ด๋ฆฌ
- ์ค์ ์ง์ค์ ๋ฒ์ ๊ด๋ฆฌ ํ์ผ
์ฝํ๋ฆฐ(Kotlin)์ JetBrains๊ฐ ๊ฐ๋ฐํ ํ๋์ ์ธ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด๋ก, Google์ด Android ๊ณต์ ์ธ์ด๋ก ์ฑํํ์ต๋๋ค.
- โ ๊ฐ๊ฒฐํจ: Java๋ณด๋ค ์ ์ ์ฝ๋๋ก ๊ฐ์ ๊ธฐ๋ฅ ๊ตฌํ
- โ ์์ ์ฑ: Null ์์ ์ฑ์ผ๋ก ๋ฐํ์ ์ค๋ฅ ๊ฐ์
- โ ์ํธ์ด์ฉ์ฑ: Java ์ฝ๋์ 100% ํธํ
- โ ํ๋์ : ํจ์ํ ํ๋ก๊ทธ๋๋ฐ, ์ฝ๋ฃจํด ๋ฑ ์ง์
// val: ๋ถ๋ณ ๋ณ์ (๊ฐ ๋ณ๊ฒฝ ๋ถ๊ฐ, Java์ final)
val name = "Android"
val count: Int = 10
// var: ๊ฐ๋ณ ๋ณ์ (๊ฐ ๋ณ๊ฒฝ ๊ฐ๋ฅ)
var score = 0
score = 100 // OK// ๊ธฐ๋ณธ ํํ
fun greet(name: String): String {
return "Hello, $name!"
}
// ๋จ์ผ ํํ์ ํจ์
fun add(a: Int, b: Int) = a + b
// ๋ฐํ๊ฐ ์๋ ํจ์
fun printMessage(msg: String): Unit { // Unit์ ์๋ต ๊ฐ๋ฅ
println(msg)
}// ๊ธฐ๋ณธ ํด๋์ค
class Person {
var name: String = ""
var age: Int = 0
}
// ์์ฑ์๊ฐ ์๋ ํด๋์ค
class Student(val name: String, var grade: Int) {
fun study() {
println("$name is studying")
}
}
// ํด๋์ค ์ฌ์ฉ
val student = Student("John", 10)
student.study() // "John is studying"// ๋ถ๋ชจ ํด๋์ค (open ํค์๋ ํ์)
open class Animal {
open fun sound() {
println("Some sound")
}
}
// ์์ ํด๋์ค
class Dog : Animal() {
override fun sound() {
println("Bark!")
}
}// Nullable ํ์
(? ๋ถ์)
var name: String? = null
name = "Kotlin"
// Non-null ํ์
(? ์์)
var age: Int = 10
// age = null // ์ปดํ์ผ ์ค๋ฅ!
// ์์ ํ ํธ์ถ
val length = name?.length // name์ด null์ด๋ฉด length๋ null
// Elvis ์ฐ์ฐ์
val len = name?.length ?: 0 // name์ด null์ด๋ฉด 0 ๋ฐํ// ๊ธฐ๋ณธ ํํ
val sum = { a: Int, b: Int -> a + b }
println(sum(3, 5)) // 8
// ๋ฆฌ์ค๋์์ ์์ฃผ ์ฌ์ฉ
button.setOnClickListener { view ->
// ๋ฒํผ ํด๋ฆญ ์ ์คํ
println("Button clicked")
}
// ํ๋ผ๋ฏธํฐ๊ฐ ํ๋์ผ ๋ it ์ฌ์ฉ
items.forEach { item ->
println(item)
}
// ๋ ๊ฐ๋จํ๊ฒ
items.forEach {
println(it) // it์ ํ์ฌ item
}// apply: ๊ฐ์ฒด ์ค์ ์ ์ ์ฉ
val person = Person().apply {
name = "John"
age = 25
}
// let: null ์ฒดํฌ์ ํจ๊ป ์ฌ์ฉ
name?.let {
println("Name is $it")
}
// with: ๊ฐ์ฒด์ ์ฌ๋ฌ ๋ฉ์๋ ํธ์ถ
with(person) {
println(name)
println(age)
}Android ์ฑ์ 4๊ฐ์ง ์ฃผ์ ๊ตฌ์ฑ ์์๋ก ์ด๋ฃจ์ด์ง๋๋ค:
- ์ฌ์ฉ์ ์ธํฐํ์ด์ค ํ๋ฉด์ ๋ด๋น
- ํ๋์ Activity = ํ๋์ ํ๋ฉด
- ์: ๋ก๊ทธ์ธ ํ๋ฉด, ๋ฉ์ธ ํ๋ฉด, ์ค์ ํ๋ฉด ๋ฑ
- ๋ฐฑ๊ทธ๋ผ์ด๋ ์์ ์ ๋ด๋น
- ํ๋ฉด ์์ด ์คํ
- ์: ์์ ์ฌ์, ํ์ผ ๋ค์ด๋ก๋ ๋ฑ
- ์์คํ ์ด๋ฒคํธ๋ฅผ ์์
- ์: ๋ฐฐํฐ๋ฆฌ ๋ถ์กฑ ์๋ฆผ, ๋คํธ์ํฌ ๋ณ๊ฒฝ ๋ฑ
- ์ฑ ๊ฐ ๋ฐ์ดํฐ ๊ณต์
- ์: ์ฐ๋ฝ์ฒ, ๊ฐค๋ฌ๋ฆฌ ๋ฐ์ดํฐ ์ ๊ทผ
์ฌ์ฉ์๊ฐ ์ฑ ์์ด์ฝ ํฐ์น
โ
Android ์์คํ
์ด ์ฑ ํ๋ก์ธ์ค ์์
โ
Application ํด๋์ค ์์ฑ (์ฑ ์ ์ฒด ์ด๊ธฐํ)
โ
AndroidManifest.xml ์ฝ๊ธฐ
โ
MAIN/LAUNCHER ์กํฐ๋นํฐ ์ฐพ๊ธฐ
โ
MainActivity ์์ฑ ๋ฐ onCreate() ํธ์ถ
โ
๋ ์ด์์ ํ์ผ(activity_main.xml) ๋ก๋
โ
ํ๋ฉด์ UI ํ์
โ
์ฌ์ฉ์ ์
๋ ฅ ๋๊ธฐ
Activity๋ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ฐ์ง๋ฉฐ, ๊ฐ ๋จ๊ณ๋ง๋ค ํธ์ถ๋๋ ์ฝ๋ฐฑ ๋ฉ์๋๊ฐ ์์ต๋๋ค:
class MainActivity : AppCompatActivity() {
// 1. ์กํฐ๋นํฐ๊ฐ ์์ฑ๋ ๋ (์ต์ด 1ํ)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ์ด๊ธฐํ ์์
: UI ์ค์ , ๋ณ์ ์ด๊ธฐํ ๋ฑ
}
// 2. ํ๋ฉด์ด ๋ณด์ด๊ธฐ ์์ํ ๋
override fun onStart() {
super.onStart()
// ๋ฆฌ์์ค ์ค๋น
}
// 3. ์ฌ์ฉ์์ ์ํธ์์ฉ ๊ฐ๋ฅํ ์ํ
override fun onResume() {
super.onResume()
// ์ ๋๋ฉ์ด์
์์, ์ผ์ ๋ฑ๋ก ๋ฑ
}
// 4. ์ผ์ ์ ์ง (๋ค๋ฅธ ์ฑ์ด foreground๋ก ์ด)
override fun onPause() {
super.onPause()
// ์ ๋๋ฉ์ด์
์ค์ง, ๋ฐ์ดํฐ ์ ์ฅ ๋ฑ
}
// 5. ํ๋ฉด์ด ๋ณด์ด์ง ์์ ๋
override fun onStop() {
super.onStop()
// ๋ฆฌ์์ค ํด์
}
// 6. ์กํฐ๋นํฐ๊ฐ ์์ ํ ์ข
๋ฃ๋ ๋
override fun onDestroy() {
super.onDestroy()
// ์ต์ข
์ ๋ฆฌ ์์
}
}onCreate() โ onStart() โ onResume() [์คํ ์ค] โ onPause() โ onStop() โ onDestroy()
โ โ
โโโโโโโโโโโโโโโโ onRestart() โโโโโโโโโโโโโโโโโโโโโโโโ
๋ชจ๋ Android ์ฑ์ AndroidManifest.xml ํ์ผ์ ๊ฐ์ ธ์ผ ํ๋ฉฐ, ๋ค์ ์ ๋ณด๋ฅผ ํฌํจํฉ๋๋ค:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.tsetapplication">
<!-- ๊ถํ ์ ์ธ -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.TsetApplication">
<!-- ์กํฐ๋นํฐ ์ ์ธ -->
<activity
android:name=".MainActivity"
android:exported="true">
<!-- ์ฑ ๋ฐ์ฒ์ ํ์ํ ๋ฉ์ธ ์กํฐ๋นํฐ ์ง์ -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- ์๋น์ค ์ ์ธ -->
<service android:name=".MyMusicService" />
</application>
</manifest>Android UI๋ XML ๋ ์ด์์๊ณผ Kotlin ์ฝ๋๋ก ๊ตฌ์ฑ๋ฉ๋๋ค:
ํ๋ฉด = Layout (XML) + Activity (Kotlin)
-
View: ํ๋ฉด์ ํ์๋๋ ๋ชจ๋ UI ์์์ ๊ธฐ๋ณธ ํด๋์ค
- ์: TextView, Button, ImageView ๋ฑ
-
ViewGroup: View๋ค์ ๋ด๋ ์ปจํ ์ด๋
- ์: LinearLayout, ConstraintLayout, FrameLayout ๋ฑ
ViewGroup (์ปจํ
์ด๋)
โโโ View (ํ
์คํธ)
โโโ View (๋ฒํผ)
โโโ ViewGroup (ํ์ ์ปจํ
์ด๋)
โโโ View (์ด๋ฏธ์ง)
โโโ View (์
๋ ฅ์ฐฝ)
<?xml version="1.0" encoding="utf-8"?>
<!-- ConstraintLayout: ์ ์ฝ ์กฐ๊ฑด์ผ๋ก View ์์น๋ฅผ ์ง์ ํ๋ ๋ ์ด์์ -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/main"
android:layout_width="match_parent" <!-- ๋ถ๋ชจ ๋๋น๋งํผ -->
android:layout_height="match_parent"> <!-- ๋ถ๋ชจ ๋์ด๋งํผ -->
<!-- TextView: ํ
์คํธ๋ฅผ ํ์ํ๋ View -->
<TextView
android:layout_width="wrap_content" <!-- ๋ด์ฉ๋งํผ -->
android:layout_height="wrap_content" <!-- ๋ด์ฉ๋งํผ -->
android:text="Hello World!"
<!-- ์ ์ฝ ์กฐ๊ฑด: ํ๋ฉด ์ค์์ ๋ฐฐ์น -->
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>| ์์ฑ | ์ค๋ช | ๊ฐ๋ฅํ ๊ฐ |
|---|---|---|
android:id |
View์ ๊ณ ์ ์๋ณ์ | @+id/view_name |
layout_width |
๋๋น | match_parent, wrap_content, 100dp |
layout_height |
๋์ด | match_parent, wrap_content, 100dp |
android:text |
ํ ์คํธ ๋ด์ฉ | "๋ฌธ์์ด" ๋๋ @string/name |
android:textSize |
๊ธ์ ํฌ๊ธฐ | 16sp |
android:textColor |
๊ธ์ ์์ | #FF0000 ๋๋ @color/red |
- ์ ์ฐํ ๋ ์ด์์
- ์ฑ๋ฅ ์ต์ ํ
- ๋ณต์กํ UI๋ ๊น์ด ์์ด ๊ตฌํ ๊ฐ๋ฅ
<androidx.constraintlayout.widget.ConstraintLayout>
<Button
android:id="@+id/button1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/button2"
app:layout_constraintTop_toBottomOf="@id/button1"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>- ์ธ๋ก ๋๋ ๊ฐ๋ก๋ก ์์๋๋ก ๋ฐฐ์น
<LinearLayout
android:orientation="vertical"> <!-- ์ธ๋ก ๋ฐฐ์น -->
<TextView android:text="์ฒซ ๋ฒ์งธ" />
<TextView android:text="๋ ๋ฒ์งธ" />
<TextView android:text="์ธ ๋ฒ์งธ" />
</LinearLayout>- View๋ฅผ ๊ฒน์ณ์ ๋ฐฐ์น
<FrameLayout>
<ImageView /> <!-- ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง -->
<TextView /> <!-- ์ด๋ฏธ์ง ์์ ํ
์คํธ -->
</FrameLayout>class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// XML์ View๋ฅผ Kotlin ์ฝ๋์์ ๊ฐ์ ธ์ค๊ธฐ
val textView = findViewById<TextView>(R.id.textView)
textView.text = "ํ
์คํธ ๋ณ๊ฒฝ"
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
// ๋ฒํผ ํด๋ฆญ ์ ์คํ
textView.text = "๋ฒํผ์ด ํด๋ฆญ๋์์ต๋๋ค"
}
}
}// build.gradle.kts์ ์ค์ ํ์
android {
buildFeatures {
viewBinding = true
}
}
// Activity์์ ์ฌ์ฉ
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// View์ ์ฝ๊ฒ ์ ๊ทผ
binding.textView.text = "ํ
์คํธ ๋ณ๊ฒฝ"
binding.button.setOnClickListener {
binding.textView.text = "๋ฒํผ ํด๋ฆญ๋จ"
}
}
}// ๋ฐฉ๋ฒ 1: setOnClickListener
button.setOnClickListener {
Toast.makeText(this, "๋ฒํผ ํด๋ฆญ!", Toast.LENGTH_SHORT).show()
}
// ๋ฐฉ๋ฒ 2: ๋๋ค ํํ์์ผ๋ก View ์ ๊ทผ
button.setOnClickListener { view ->
view.isEnabled = false // ๋ฒํผ ๋นํ์ฑํ
}val editText = findViewById<EditText>(R.id.editText)
// ํ
์คํธ ๋ณ๊ฒฝ ๊ฐ์ง
editText.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
println("์
๋ ฅ๋ ํ
์คํธ: ${s.toString()}")
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})// ์งง์ ๋ฉ์์ง (2์ด)
Toast.makeText(this, "์ ์ฅ๋์์ต๋๋ค", Toast.LENGTH_SHORT).show()
// ๊ธด ๋ฉ์์ง (3.5์ด)
Toast.makeText(this, "์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค", Toast.LENGTH_LONG).show()Snackbar.make(view, "์ญ์ ๋์์ต๋๋ค", Snackbar.LENGTH_LONG)
.setAction("์ทจ์") {
// ์ทจ์ ๋ฒํผ ํด๋ฆญ ์ ์คํ
println("์ญ์ ์ทจ์")
}
.show()// ๊ฐ๋จํ ์ด๋
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
// ๋ฐ์ดํฐ ์ ๋ฌ
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("name", "John")
intent.putExtra("age", 25)
startActivity(intent)
// SecondActivity์์ ๋ฐ์ดํฐ ๋ฐ๊ธฐ
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val name = intent.getStringExtra("name")
val age = intent.getIntExtra("age", 0)
println("Name: $name, Age: $age")
}
}RecyclerView๋ ๊ธด ๋ชฉ๋ก์ ํจ์จ์ ์ผ๋ก ํ์ํ๋ ๋ทฐ์ ๋๋ค.
// 1. ๋ฐ์ดํฐ ํด๋์ค
data class Item(val title: String, val description: String)
// 2. ViewHolder
class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val titleText: TextView = view.findViewById(R.id.titleText)
val descText: TextView = view.findViewById(R.id.descText)
}
// 3. Adapter
class ItemAdapter(private val items: List<Item>) :
RecyclerView.Adapter<ItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_layout, parent, false)
return ItemViewHolder(view)
}
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = items[position]
holder.titleText.text = item.title
holder.descText.text = item.description
}
override fun getItemCount() = items.size
}
// 4. Activity์์ ์ฌ์ฉ
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = ItemAdapter(itemList)ํ์ผ ์์น: mobile/src/main/java/com/example/tsetapplication/MainActivity.kt
package com.example.tsetapplication
// ํ์ํ ํด๋์ค๋ค์ import (๊ฐ์ ธ์ค๊ธฐ)
import android.os.Bundle // ๋ฐ์ดํฐ ์ ๋ฌ์ฉ
import androidx.activity.enableEdgeToEdge // ์ ์ฒด ํ๋ฉด ๋ชจ๋
import androidx.appcompat.app.AppCompatActivity // ๊ธฐ๋ณธ Activity ํด๋์ค
import androidx.core.view.ViewCompat // View ํธํ์ฑ ๋๊ตฌ
import androidx.core.view.WindowInsetsCompat // ์์คํ
๋ฐ ์ ๋ณด
// MainActivity ํด๋์ค ์ ์ธ
// : AppCompatActivity() = AppCompatActivity๋ฅผ ์์๋ฐ์
class MainActivity : AppCompatActivity() {
// override = ๋ถ๋ชจ ํด๋์ค์ ๋ฉ์๋๋ฅผ ์ฌ์ ์
// onCreate = Activity๊ฐ ์์ฑ๋ ๋ ํธ์ถ๋๋ ๋ฉ์๋
override fun onCreate(savedInstanceState: Bundle?) {
// ๋ถ๋ชจ ํด๋์ค์ onCreate ๋จผ์ ํธ์ถ (ํ์!)
super.onCreate(savedInstanceState)
// Edge-to-Edge ๋ชจ๋ ํ์ฑํ
// ์ํ๋ฐ, ๋ด๋น๊ฒ์ด์
๋ฐ ๋ค๊น์ง ์ปจํ
์ธ ํ์
enableEdgeToEdge()
// XML ๋ ์ด์์ ํ์ผ์ ํ๋ฉด์ ํ์
// R.layout.activity_main = res/layout/activity_main.xml
setContentView(R.layout.activity_main)
// ์์คํ
๋ฐ(์ํ๋ฐ, ๋ด๋น๊ฒ์ด์
๋ฐ) ์์ญ ์ฒ๋ฆฌ
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
// ์์คํ
๋ฐ์ ํฌ๊ธฐ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
// View์ ํจ๋ฉ ์ค์ (์์คํ
๋ฐ์ ๊ฐ๋ ค์ง์ง ์๋๋ก)
v.setPadding(
systemBars.left, // ์ผ์ชฝ ํจ๋ฉ
systemBars.top, // ์์ชฝ ํจ๋ฉ
systemBars.right, // ์ค๋ฅธ์ชฝ ํจ๋ฉ
systemBars.bottom // ์๋์ชฝ ํจ๋ฉ
)
// insets๋ฅผ ๋ฐํ (๋ค๋ฅธ ๋ฆฌ์ค๋์์๋ ์ฌ์ฉ ๊ฐ๋ฅํ๋๋ก)
insets
}
}
}- ์ฌ์ฉ์๊ฐ ์ฑ ์์ด์ฝ ํฐ์น
- Android ์์คํ ์ด MainActivity ์ธ์คํด์ค ์์ฑ
onCreate()๋ฉ์๋ ํธ์ถenableEdgeToEdge()- ์ ์ฒด ํ๋ฉด ๋ชจ๋ ํ์ฑํsetContentView()- activity_main.xml ๋ ์ด์์ ๋ก๋setOnApplyWindowInsetsListener()- ์์คํ ๋ฐ ์์ญ ํจ๋ฉ ์ค์ - ํ๋ฉด ํ์ ์๋ฃ
AppCompatActivity
- ๋ชจ๋ Activity์ ๊ธฐ๋ณธ ํด๋์ค
- Android ์ด์ ๋ฒ์ ๊ณผ์ ํธํ์ฑ ์ ๊ณต
- ActionBar, ํ ๋ง ๋ฑ์ ๊ธฐ๋ฅ ํฌํจ
Bundle
- ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ณ ์ ๋ฌํ๋ ์ปจํ ์ด๋
- Activity ์ฌ์์ฑ ์ ์ํ ๋ณต์์ ์ฌ์ฉ
savedInstanceState๋ก ์ด์ ์ํ ๋ฐ์
findViewById()
- XML์ ์ ์๋ View๋ฅผ Kotlin ์ฝ๋์์ ์ฐพ๋ ๋ฉ์๋
- ID๋ก View๋ฅผ ๊ฒ์
- ๋ฐํ ํ์ : View (ํ๋ณํ ํ์)
ํ์ผ ์์น: mobile/src/main/res/layout/activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- XML ์ ์ธ: ํ์ผ ๋ฒ์ ๊ณผ ์ธ์ฝ๋ฉ ์ง์ -->
<!-- ConstraintLayout: ์ ์ฝ ์กฐ๊ฑด ๊ธฐ๋ฐ ๋ ์ด์์ -->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
<!-- View์ ๊ณ ์ ID (MainActivity์์ findViewById๋ก ์ฐพ์ ๋ ์ฌ์ฉ) -->
android:id="@+id/main"
<!-- ๋ ์ด์์ ํฌ๊ธฐ -->
android:layout_width="match_parent" <!-- ๋ถ๋ชจ ๋๋น๋งํผ (ํ๋ฉด ์ ์ฒด) -->
android:layout_height="match_parent" <!-- ๋ถ๋ชจ ๋์ด๋งํผ (ํ๋ฉด ์ ์ฒด) -->
<!-- tools ๋ค์์คํ์ด์ค: ๊ฐ๋ฐ ์์๋ง ์ฌ์ฉ, ์ค์ ์ฑ์๋ ์ํฅ ์์ -->
tools:context=".MainActivity">
<!-- TextView: ํ
์คํธ๋ฅผ ํ์ํ๋ View -->
<TextView
<!-- wrap_content: ๋ด์ฉ๋ฌผ ํฌ๊ธฐ๋งํผ -->
android:layout_width="wrap_content"
android:layout_height="wrap_content"
<!-- ํ์ํ ํ
์คํธ -->
android:text="Hello World!"
<!-- ConstraintLayout ์ ์ฝ ์กฐ๊ฑด๋ค -->
<!-- Bottom์ parent์ Bottom์ ์ฐ๊ฒฐ -->
app:layout_constraintBottom_toBottomOf="parent"
<!-- End(์ค๋ฅธ์ชฝ)๋ฅผ parent์ End์ ์ฐ๊ฒฐ -->
app:layout_constraintEnd_toEndOf="parent"
<!-- Start(์ผ์ชฝ)๋ฅผ parent์ Start์ ์ฐ๊ฒฐ -->
app:layout_constraintStart_toStartOf="parent"
<!-- Top์ parent์ Top์ ์ฐ๊ฒฐ -->
app:layout_constraintTop_toTopOf="parent" />
<!-- ๋ค ๋ฐฉํฅ ๋ชจ๋ parent ์ค์์ ์ฐ๊ฒฐ โ ํ๋ฉด ์ ์ค์์ ๋ฐฐ์น -->
</androidx.constraintlayout.widget.ConstraintLayout>ํ๋ฉด (parent)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ โ โ Top
โ TextView โ โ ๋ชจ๋ ๋ฐฉํฅ์ด parent์ ์ฐ๊ฒฐ๋์ด
โ "Hello World!" โ ํ๋ฉด ์ ์ค์์ ์์น
โ โ โ Bottom
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Start โ โ End
ํ์ผ ์์น: shared/src/main/java/com/example/tsetapplication/shared/MyMusicService.kt
package com.example.tsetapplication.shared
// ํ์ํ ํด๋์ค import
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat.MediaItem
import androidx.media.MediaBrowserServiceCompat
import android.support.v4.media.session.MediaSessionCompat
import java.util.ArrayList
/**
* MyMusicService: ์์
์ฌ์ ์๋น์ค
*
* MediaBrowserServiceCompat๋ฅผ ์์๋ฐ์ ๊ตฌํ
* - Android Auto, Automotive์์ ์์
ํ์ ๊ฐ๋ฅ
* - ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์์
์ฌ์
* - ๋ค๋ฅธ ์ฑ(์: Android Auto)์์ ์ ์ด ๊ฐ๋ฅ
*/
class MyMusicService : MediaBrowserServiceCompat() {
// MediaSession: ๋ฏธ๋์ด ์ฌ์ ์ํ ๊ด๋ฆฌ
// lateinit: ๋์ค์ ์ด๊ธฐํํ ๋ณ์ (onCreate์์ ์ด๊ธฐํ)
private lateinit var session: MediaSessionCompat
// callback: ์ฌ์ฉ์ ๋์(์ฌ์, ์ผ์์ ์ง ๋ฑ)์ ์ฒ๋ฆฌํ๋ ๊ฐ์ฒด
// object: ์ต๋ช
ํด๋์ค ์ธ์คํด์ค ์์ฑ
private val callback = object : MediaSessionCompat.Callback() {
// ์ฌ์ ๋ฒํผ์ ๋๋ ์ ๋
override fun onPlay() {
// TODO: ์ค์ ์์
์ฌ์ ๋ก์ง ๊ตฌํ
}
// ํน์ ๊ณก์ผ๋ก ์ด๋
override fun onSkipToQueueItem(queueId: Long) {}
// ํน์ ์์น๋ก ์ด๋ (์: 30์ด๋ก ์ด๋)
override fun onSeekTo(position: Long) {}
// ๋ฏธ๋์ด ID๋ก ์ฌ์
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {}
// ์ผ์์ ์ง
override fun onPause() {}
// ์ ์ง
override fun onStop() {}
// ๋ค์ ๊ณก
override fun onSkipToNext() {}
// ์ด์ ๊ณก
override fun onSkipToPrevious() {}
// ์ฌ์ฉ์ ์ ์ ๋์
override fun onCustomAction(action: String?, extras: Bundle?) {}
// ๊ฒ์์ด๋ก ์ฌ์ (์: "๋นํ์ฆ ๋
ธ๋ ํ์ด์ค")
override fun onPlayFromSearch(query: String?, extras: Bundle?) {}
}
// ์๋น์ค๊ฐ ์์ฑ๋ ๋ ํธ์ถ (์ต์ด 1ํ)
override fun onCreate() {
super.onCreate()
// MediaSession ์์ฑ
session = MediaSessionCompat(this, "MyMusicService")
// Session Token ์ค์ (๋ค๋ฅธ ์ฑ์ด ์ด ์๋น์ค๋ฅผ ์ฐพ์ ์ ์๊ฒ ํจ)
sessionToken = session.sessionToken
// Callback ๋ฑ๋ก (์ฌ์ฉ์ ๋์ ์ฒ๋ฆฌ)
session.setCallback(callback)
// ํ๋๊ทธ ์ค์
session.setFlags(
// ๋ฏธ๋์ด ๋ฒํผ ์ฒ๋ฆฌ (ํค๋์
๋ฒํผ ๋ฑ)
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
// ์ ์ก ์ ์ด ์ฒ๋ฆฌ (์ฌ์, ์ผ์์ ์ง ๋ฑ)
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
}
// ์๋น์ค๊ฐ ์ข
๋ฃ๋ ๋ ํธ์ถ
override fun onDestroy() {
// MediaSession ๋ฆฌ์์ค ํด์
session.release()
}
/**
* onGetRoot: ํด๋ผ์ด์ธํธ๊ฐ ์๋น์ค์ ์ฐ๊ฒฐํ ๋ ํธ์ถ
*
* @param clientPackageName ์ฐ๊ฒฐ ์์ฒญํ ์ฑ์ ํจํค์ง๋ช
* @param clientUid ํด๋ผ์ด์ธํธ ์ฑ์ UID
* @param rootHints ํด๋ผ์ด์ธํธ๊ฐ ์ ๊ณตํ ํํธ
* @return BrowserRoot ๊ฐ์ฒด (์ฐ๊ฒฐ ํ์ฉ ์) ๋๋ null (๊ฑฐ๋ถ ์)
*/
override fun onGetRoot(
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): MediaBrowserServiceCompat.BrowserRoot? {
// "root"๋ผ๋ ID๋ก ๋ฃจํธ ๋ฐํ (๋ชจ๋ ํด๋ผ์ด์ธํธ ํ์ฉ)
// ์ค์ ์ฑ์์๋ clientPackageName์ ๊ฒ์ฌํ์ฌ ๋ณด์ ๊ฐํ ํ์
return MediaBrowserServiceCompat.BrowserRoot("root", null)
}
/**
* onLoadChildren: ๋ฏธ๋์ด ํญ๋ชฉ ๋ชฉ๋ก์ ์์ฒญํ ๋ ํธ์ถ
*
* @param parentId ๋ถ๋ชจ ๋ฏธ๋์ด ํญ๋ชฉ์ ID
* @param result ๊ฒฐ๊ณผ๋ฅผ ์ ๋ฌํ ๊ฐ์ฒด
*/
override fun onLoadChildren(
parentId: String,
result: Result<MutableList<MediaItem>>
) {
// ๋น ๋ฆฌ์คํธ ๋ฐํ (์ค์ ๋ก๋ ์์
๋ชฉ๋ก์ ๋ฐํํด์ผ ํจ)
// TODO: ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ํ์ผ์์ ์์
๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ
result.sendResult(ArrayList())
}
}โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Android Auto / ์๋์ฐจ โ
โ (ํด๋ผ์ด์ธํธ) โ
โโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ
โ ์ฐ๊ฒฐ ์์ฒญ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ MyMusicService โ
โ (์๋น์ค) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ onGetRoot() โ ์ฐ๊ฒฐ ํ์ฉ? โ
โ onLoadChildren() โ ๋ชฉ๋ก โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ MediaSession โ
โ - callback โ
โ - onPlay() โ
โ - onPause() โ
โ - onSkipToNext() โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ
์ค์ ์์
์ฌ์
R ํด๋์ค๋ Android๊ฐ ์๋์ผ๋ก ์์ฑํ๋ ํด๋์ค๋ก, ๋ชจ๋ ๋ฆฌ์์ค์ ์ ๊ทผํ ์ ์๋ ID๋ฅผ ์ ๊ณตํฉ๋๋ค.
// R ํด๋์ค ๊ตฌ์กฐ (์๋ ์์ฑ๋จ, ์ง์ ์์ ๋ถ๊ฐ)
class R {
class layout {
const val activity_main = 0x7f030001
}
class id {
const val main = 0x7f070001
const val textView = 0x7f070002
}
class string {
const val app_name = 0x7f0a0001
}
class drawable {
const val ic_launcher = 0x7f020001
}
}
// ์ฌ์ฉ ์
setContentView(R.layout.activity_main) // ๋ ์ด์์ ํ์ผ
val view = findViewById(R.id.textView) // View ID
val appName = getString(R.string.app_name) // ๋ฌธ์์ด ๋ฆฌ์์ค-
ํ๋ก์ ํธ ์ด๊ธฐ
- Android Studio ์คํ
Open an Existing Project์ ํTsetApplicationํด๋ ์ ํ
-
Gradle ๋๊ธฐํ
- ํ๋ก์ ํธ๊ฐ ์ด๋ฆฌ๋ฉด ์๋์ผ๋ก Gradle ๋๊ธฐํ ์์
- ๋๋
File โ Sync Project with Gradle Files
-
์๋ฎฌ๋ ์ดํฐ ๋๋ ์ค์ ๊ธฐ๊ธฐ ์ฐ๊ฒฐ
- ์๋ฎฌ๋ ์ดํฐ:
Tools โ Device Manager โ Create Device - ์ค์ ๊ธฐ๊ธฐ: USB ์ฐ๊ฒฐ + ๊ฐ๋ฐ์ ์ต์ ํ์ฑํ
- ์๋ฎฌ๋ ์ดํฐ:
-
์ฑ ์คํ
- ์๋จ ํด๋ฐ์์ ์คํ ๋ฒํผ (โถ) ํด๋ฆญ
- ๋๋
Shift + F10(Windows/Linux),Ctrl + R(Mac)
# Unix/Mac
./gradlew build
# Windows
gradlew.bat build# ์ฑ ๋น๋ (APK ์์ฑ)
./gradlew assembleDebug # ๋๋ฒ๊ทธ APK
./gradlew assembleRelease # ๋ฆด๋ฆฌ์ค APK
# ์ฑ ์ค์น ๋ฐ ์คํ
./gradlew installDebug # ๋๋ฒ๊ทธ APK ์ค์น
./gradlew installRelease # ๋ฆด๋ฆฌ์ค APK ์ค์น
# ํ
์คํธ ์คํ
./gradlew test # ๋จ์ ํ
์คํธ
./gradlew connectedAndroidTest # ๊ธฐ๊ธฐ ํ
์คํธ
# ๋น๋ ์ ๋ฆฌ
./gradlew clean # ๋น๋ ๊ฒฐ๊ณผ๋ฌผ ์ญ์
# ์์กด์ฑ ํ์ธ
./gradlew dependencies # ์์กด์ฑ ํธ๋ฆฌ ์ถ๋ ฅ๋น๋๊ฐ ์๋ฃ๋๋ฉด APK ํ์ผ์ด ๋ค์ ์์น์ ์์ฑ๋ฉ๋๋ค:
mobile/build/outputs/apk/debug/mobile-debug.apk
Android Studio ํ๋จ์ Logcat ํญ์์ ์ฑ ๋ก๊ทธ๋ฅผ ์ค์๊ฐ์ผ๋ก ํ์ธํ ์ ์์ต๋๋ค.
// ์ฝ๋์์ ๋ก๊ทธ ์ถ๋ ฅ
import android.util.Log
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ๋ก๊ทธ ์ถ๋ ฅ
Log.d("MainActivity", "onCreate ํธ์ถ๋จ") // Debug
Log.i("MainActivity", "์ ๋ณด ๋ก๊ทธ") // Info
Log.w("MainActivity", "๊ฒฝ๊ณ ๋ก๊ทธ") // Warning
Log.e("MainActivity", "์๋ฌ ๋ก๊ทธ") // Error
}
}Logcat ํํฐ:
# ์ฑ์ ๋ก๊ทธ๋ง ๋ณด๊ธฐ
package:com.example.tsetapplication
# ํน์ ํ๊ทธ๋ง ๋ณด๊ธฐ
tag:MainActivity
# ์๋ฌ๋ง ๋ณด๊ธฐ
level:ERROR
- Kotlin ๊ธฐ๋ณธ ๋ฌธ๋ฒ ํ์ต
- Activity์ Lifecycle ์ดํด
- Layout๊ณผ View ํ์ต
- RecyclerView๋ก ๋ฆฌ์คํธ ๊ตฌํ
- ViewModel๊ณผ LiveData (MVVM ํจํด)
- Room ๋ฐ์ดํฐ๋ฒ ์ด์ค
- Retrofit์ผ๋ก ๋คํธ์ํฌ ํต์
- Coroutine์ผ๋ก ๋น๋๊ธฐ ์ฒ๋ฆฌ
File โ Invalidate Caches / Restart โ Invalidate and Restart
./gradlew clean
./gradlew buildlocal.properties ํ์ผ ํ์ธ:
sdk.dir=/Users/์ฌ์ฉ์๋ช
/Library/Android/sdk- Hardware Acceleration ํ์ฑํ (Intel HAXM, AMD Hypervisor)
- RAM ํ ๋น๋ ์ฆ๊ฐ (AVD Manager์์ ์ค์ )
- x86 ์ด๋ฏธ์ง ์ฌ์ฉ (ARM๋ณด๋ค ๋น ๋ฆ)
์ด ๊ฐ์ด๋๊ฐ Android ๊ฐ๋ฐ์ ์์ํ๋ ๋ฐ ๋์์ด ๋๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ๊ถ๊ธํ ์ ์ด ์์ผ๋ฉด ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ๊ฑฐ๋, ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํด๋ณด์ธ์!
ํ๋ณตํ ์ฝ๋ฉ ๋์ธ์! ๐