MainActivity 의 onCreate 에서 시작한다. setContentView 에 webView 를 추가한다.
오류난다. 인터넷 권한 허용해야 한다.
AndroidManifest.xml 열어 아래 코드 추가한다. 태그 위에 추가한다.
plugins { // Gradle 은 플러그인 시스템을 기반으로 동작한다.
    alias(libs.plugins.android.application) apply false // 앱 생성을 지원한다. apply false 는 플러그인을 하위 프로젝트 및 모듈에만 적용하고 프로젝트 루트 수준엔 적용하지 않는다는 의미.
    alias(libs.plugins.kotlin.android) apply false // 프로젝트에서 코틀린 지원 제공
}plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.kotlin.android)
}
android {
    namespace = "com.example.myapplication" // 프로젝트 패키지 명. 빌드 및 리소스 식별자 생성에 사용
    compileSdk = 36 // 앱 컴파일 API 레벨. 하위호환 제공
    defaultConfig {
        applicationId = "com.example.myapplication" // 구글플레이에서의 앱 아이디
        minSdk = 24 // 앱이 지원하는 최소 API 레벨. 버전 낮은 기기에서는 이 값에 의해 구글플레이에 노출되지 않을 수 있음.
        targetSdk = 36 // 앱의 타겟 API 레벨. 가장 최적화된 버전 표시
        versionCode = 1 // 앱 업데이트 시 하나 업데이트 해야 함.
        versionName = "1.0" // 버전 이름. X.Y.Z 로 표기
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" // UI 테스트에 활용할 실행기
    }
    buildTypes { // 릴리즈 빌드 관련
        release {
            isMinifyEnabled = false // true 로 하면 사용 안하는 코드 제거 후 앱 난독화하여 앱 크기 줄임
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions { // 자바, 바이트코드 언어 수준 명시
        sourceCompatibility = JavaVersion.VERSION_11
        targetCompatibility = JavaVersion.VERSION_11
    }
    kotlinOptions { // kotlin gradle 플러그인에서 사용할 jvm 라이브러리를 나타낸다.
        jvmTarget = "11"
    }
}
dependencies { // SDK 내 앱이 사용할 라이브러리 지정. 버전 표기는 groupId, artifactId, versionId 를 : 로 구분
    implementation(libs.androidx.core.ktx) // 젯팩컴포즈
    implementation(libs.androidx.appcompat) // 하위호환 + 젯팩컴포즈
    implementation(libs.material) // Material 디자인 구성요소
    implementation(libs.androidx.activity) // 액티비티
    implementation(libs.androidx.constraintlayout) // ConstraintLayout ViewGroup
    testImplementation(libs.junit) // Unit-Test 라이브러리
    androidTestImplementation(libs.androidx.junit) // UI-Test 라이브러리
    androidTestImplementation(libs.androidx.espresso.core) // 테스트 관련 에스프레소 라이브러리
}pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}
rootProject.name = "My Application"
include(":app")<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Base.Theme.MyApplication" parent="Theme.Material3.DayNight.NoActionBar">
        <!-- Customize your light theme here. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryVariant">@color/purple_700</item>
        <item name="colorOnPrimary">@color/white</item>
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
    </style>
    <style name="Theme.MyApplication" parent="Base.Theme.MyApplication" />
</resources>AppCompatActivity 는 안드로이드 액티비티 클래스로 액티비티 Life-Cycle 을 관리(콜백 함수 실행)한다. 처음 프로젝트 생성 시 만들어지는 아래와 비슷하며, 각각의 의미는 주석으로 달아놓았다.
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) { // 이전에 저장한 상태를 Bundle 매개변수에 저장해서 전달해준다.
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main) // 액티비티에 표시하려는 레이아웃을 로드한다.
    }
}최초 생성되는 파일들을 더 살펴보자.
ConstraintsLayout 은 제약조건으로 뷰를 그리는데 상당히 유용하다. XML Namespace 3 개가 나오니 살펴보자.
webp 파일은 각기 다른 픽셀 밀도를 가진 기기에 이미지를 대응하기 위한 파일이다. 구글에 의해 만들어 졌으며, PNG 에 비해 압축률이 크다. 위의 ic_launcher_background.xml 처럼 벡터 형식 이전에는 webp 파일을 사용했다.
픽셀 밀도는 다음과 같다.
밑으로 갈수록 (tvdpi 빼고) 픽셀의 수가 높고, 더 큰 화면에 대응한다. 픽셀6 에뮬의 픽셀 밀도가 411 dpi 인데 이는 xxhdpi(480dpi) 를 사용한다.
픽셀 밀도를 대체하는 법은 Bitmap-Drawable 이라는 것도 있다. 스케일 비율 3:4:6:8:12:16 을 따라야 한다.
Tip : 런쳐 아이콘은 일반 이미지보다 약간 크게 생성한다. 일부 런처는 이미지를 확대해 사용할 수 있기 때문에 흐려지는 경우가 있다. 이를 방지하자.
앱에 사용되는 리소스는 아래와 같다.
<style name="screen_layout_margin">
    <item name="android:layout_margin">12dp</item>
</style>Text 를 입력하는 데엔 TextView, EditText 등을 사용할 수 있다. 디자인 적용을 더 하고 싶으면 TextInputLayout, TextInputEditText 를 사용하자. Button 말고 MaterialButton 도 좋은 선택이다.
TextView, EditTextView 유효성 검사는 중요하다. 아래 속성들은 미리 입력을 제한하는 속성들이다.
주요 파일만 작성함.
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        findViewById<Button>(R.id.calculateButton)?.setOnClickListener {
            fun getText(textView: TextView): String {
                return textView.text.toString().trim()
            }
            val tRed = findViewById<TextView>(R.id.redText)
            val tGreen = findViewById<TextView>(R.id.greenText)
            val tBlue = findViewById<TextView>(R.id.blueText)
            val colorHex = "#${getText(tRed)}${getText(tGreen)}${getText(tBlue)}"
            val color = colorHex.toColorInt()
            findViewById<Button>(R.id.calculateButton)?.setBackgroundColor(color)
        }
    }
}<?xml version="1.0" encoding="utf-8"?>
<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"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    style="@style/screen_layout"
    tools:context=".MainActivity">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/channel_red"
        android:id="@+id/redTitle"
        style="@style/text_view_margin"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/redChannel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/redTitle"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" >
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/redText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/hint_red"
            android:maxLength="2"
            android:digits="0123456789ABCDEF" />
    </com.google.android.material.textfield.TextInputLayout>
    <TextView
        android:id="@+id/greenTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/channel_green"
        style="@style/text_view_margin"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/redChannel" />
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/greenChannel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/greenTitle"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/greenText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/hint_green"
            android:maxLength="2"
            android:digits="0123456789ABCDEF" />
    </com.google.android.material.textfield.TextInputLayout>
    <TextView
        android:id="@+id/blueTitle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/channel_blue"
        style="@style/text_view_margin"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/greenChannel" />
    <com.google.android.material.textfield.TextInputLayout
        android:id="@+id/blueChannel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/blueTitle"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent">
        <com.google.android.material.textfield.TextInputEditText
            android:id="@+id/blueText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/hint_blue"
            android:maxLength="2"
            android:digits="0123456789ABCDEF" />
    </com.google.android.material.textfield.TextInputLayout>
    <com.google.android.material.button.MaterialButton
        android:id="@+id/calculateButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/button_title_calculate"
        style="@style/button_margin"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@id/blueChannel"/>
</androidx.constraintlayout.widget.ConstraintLayout>