我會以你上傳的 MainActivity(20260814-003651).kt 為主,內容重點是 Android Activity、生命週期的 onCreate()、XML Layout 載入、Edge-to-Edge 全螢幕顯示,以及 WindowInsets 系統列安全區域處理。
前面幾天我們花了不少時間學習 Kotlin,從變數、流程控制、集合、函式、Lambda、物件導向一路走到 Coroutine。
但是學 Kotlin 的目的,當然不只是一直在 Console 裡:
println("Hello Kotlin")
今天開始,我們終於要把前面學到的 Kotlin 真正帶進 Android App 開發。
而進入 Android 世界之後,第一個一定會遇到的 Kotlin 類別,就是:
MainActivity
今天就從 Android Studio 建立專案後自動產生的 MainActivity.kt 開始,了解:
Activity 到底是什麼?MainActivity 為什麼是 App 的重要入口?onCreate() 什麼時候執行?setContentView() 在做什麼?R.layout.activity_main 又是什麼?enableEdgeToEdge() 有什麼用途?WindowInsets?
學習 Kotlin 基礎時,我們可能會寫:
fun main() {
println("Hello Android")
}
一般 Kotlin 程式可以從:
fun main()
開始執行。
但是 Android App 並不是單純執行一個 main() 函式。
Android 有自己的一套應用程式架構與生命週期管理方式。
例如使用者:
點擊 App
↓
Android 啟動應用程式
↓
建立 Activity
↓
執行 Activity 的生命週期
↓
顯示畫面
因此進入 Android 開發之後,我們首先要認識的就是:
Activity
可以先把 Activity 想成 Android App 裡的一個「畫面」。
例如:
登入畫面
LoginActivity
主畫面
MainActivity
設定畫面
SettingsActivity
商品詳細資料
ProductActivity
不同 Activity 可以負責不同的使用者操作畫面。
而 Android Studio 建立基本專案之後,通常會看到:
class MainActivity : AppCompatActivity()
這就是今天的主角。
這次範例的完整程式如下:
package com.example.myapplication
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContentView(R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main)
) { v, insets ->
val systemBars =
insets.getInsets(
WindowInsetsCompat.Type.systemBars()
)
v.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
insets
}
}
}
第一次看到可能覺得:
「怎麼只是顯示一個 Android 畫面,就有這麼多東西?」
其實拆開來看並不複雜。
第一行:
package com.example.myapplication
package 用來表示這個 Kotlin 類別所屬的套件。
可以把 package 想成程式碼的分類方式。
例如:
com.example.myapplication
│
├── MainActivity.kt
├── LoginActivity.kt
├── User.kt
└── Product.kt
當專案越來越大的時候,也可以進一步分類:
com.example.myapplication
│
├── ui
├── model
├── network
├── database
└── bluetooth
因此 package 不只是名稱,它也是大型 Android 專案整理程式碼的重要基礎。
接下來看到:
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
這與前面學 Kotlin 時使用其他類別的概念相同。
Android Framework 與 AndroidX 已經提供大量功能,我們只需要將需要的類別 import 進來。
這次主要使用:
Bundle
AppCompatActivity
enableEdgeToEdge
ViewCompat
WindowInsetsCompat
後面會逐一看到它們的用途。
最重要的一行:
class MainActivity : AppCompatActivity()
如果前面已經學過 Kotlin Class 與繼承,這裡應該開始有熟悉的感覺了。
Kotlin 使用:
:
表示繼承。
也就是:
MainActivity
↓
繼承
↓
AppCompatActivity
所以:
class MainActivity : AppCompatActivity()
可以理解成:
MainActivity 是一個 Activity,而且繼承 AppCompatActivity 所提供的 Android Activity 功能。
這也正好把前面學過的 Kotlin 物件導向概念正式接到 Android。
Activity 並不是建立之後就一直存在。
Android 會根據使用者操作以及系統狀態,呼叫 Activity 不同的生命週期方法。
常見生命週期包括:
onCreate()
↓
onStart()
↓
onResume()
↓
使用者正在操作 App
↓
onPause()
↓
onStop()
↓
onDestroy()
可以簡化理解為:
建立
↓
顯示
↓
可以操作
↓
離開畫面
↓
停止
↓
銷毀
今天先從最重要的:
onCreate()
開始。
程式中:
override fun onCreate(savedInstanceState: Bundle?) {
onCreate() 是 Activity 建立時的重要生命週期 Callback。
我們通常會在這裡進行畫面的初始設定。
例如:
設定 Layout
初始化 View
設定按鈕事件
建立資料
設定 RecyclerView
初始化 ViewModel
等等。
所以未來寫 Android 時,會非常常看到:
override fun onCreate(savedInstanceState: Bundle?) {
}
前面學 Kotlin 物件導向時,我們學過:
override
現在 Android 馬上就用到了。
override fun onCreate(...)
代表:
MainActivity 要覆寫父類別 AppCompatActivity 所提供的 onCreate()。
也就是:
AppCompatActivity
↓
提供 onCreate()
↓
MainActivity
↓
override onCreate()
所以前面學習 Kotlin 的:
Class
Inheritance
Override
並不是只為了考語法。
進入 Android 之後,馬上就會大量使用。
仔細看:
savedInstanceState: Bundle?
這裡又出現前面學過的 Kotlin Nullable。
Bundle?
最後面的:
?
代表:
Bundle 可以有值
也可以是 null
所以 Android 的程式碼其實就是把前面學習的 Kotlin 基礎不斷組合起來。
例如這一行已經包含:
override
fun
參數
型別
Nullable
這也是為什麼先學 Kotlin 基礎非常重要。
進入函式後第一行:
super.onCreate(savedInstanceState)
super 指的是父類別。
我們的 MainActivity:
class MainActivity : AppCompatActivity()
父類別就是:
AppCompatActivity
所以:
super.onCreate(savedInstanceState)
就是呼叫父類別的 onCreate()。
可以簡單理解成:
先讓 AppCompatActivity
完成 Activity 本身需要的初始化
然後
MainActivity
再進行自己的設定
這也是 Android Activity 初始化的重要部分。
接下來:
enableEdgeToEdge()
這是現在 Android 專案很常看到的一行。
所謂:
Edge-to-Edge
就是讓 App 的內容可以延伸到螢幕邊緣以及系統列所在區域。
傳統畫面可能比較像:
┌─────────────────────┐
│ Status Bar │
├─────────────────────┤
│ │
│ App Content │
│ │
├─────────────────────┤
│ Navigation Bar │
└─────────────────────┘
Edge-to-Edge 則可以讓內容延伸到更完整的螢幕區域:
┌─────────────────────┐
│ │
│ │
│ App Content │
│ │
│ │
└─────────────────────┘
可以讓 App 畫面更符合現代 Android 的全螢幕設計方式。
既然內容可以延伸到系統列區域,就可能發生:
文字被 Status Bar 擋住
Button 跑到 Navigation Bar 底下
UI 被系統操作區域覆蓋
例如:
┌─────────────────────┐
│ 12:30 Wi-Fi 🔋 │
│ 我的標題 ← 被壓住 │
│ │
│ │
│ Button │
│_____________________│
所以只呼叫:
enableEdgeToEdge()
還不夠。
我們還需要知道:
系統列到底佔用了多少空間?
這就是接下來 WindowInsets 要處理的事情。
接下來是 Android 初學最重要的一行之一:
setContentView(R.layout.activity_main)
它的意思可以先簡單理解成:
MainActivity 要使用 activity_main.xml 當作畫面。
Android 傳統 View 系統通常會把:
程式邏輯
與:
畫面 Layout
分開。
例如:
MainActivity.kt
↓
程式邏輯
activity_main.xml
↓
UI 畫面
然後透過:
setContentView(R.layout.activity_main)
將兩者連接起來。
第一次看到:
R.layout.activity_main
通常會覺得很奇怪。
可以拆成:
R
↓
layout
↓
activity_main
Android 專案中的:
res/layout/activity_main.xml
會被 Android 資源系統識別。
因此程式裡可以透過:
R.layout.activity_main
取得這個 Layout 資源。
也就是:
res
└── layout
└── activity_main.xml
↓
R.layout.activity_main
所以:
setContentView(R.layout.activity_main)
就是:
MainActivity
↓
載入
↓
activity_main.xml
↓
顯示畫面
下一段:
findViewById(R.id.main)
這裡又看到一個:
R.id
如果 XML 裡某個 View 設定:
android:id="@+id/main"
Kotlin 就可以使用:
R.id.main
找到它。
因此:
findViewById(R.id.main)
就是:
從目前畫面裡找到 ID 為 main 的 View。
概念如下:
XML
android:id="@+id/main"
↓
Android Resource
R.id.main
↓
Kotlin
findViewById(R.id.main)
這是 Android XML 與 Kotlin 程式互相連接的重要概念。
接著進入今天比較新的 Android UI 處理:
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main)
) { v, insets ->
}
這段主要目的是取得系統視窗所占用的安全區域資訊。
這裡又出現前面學過的:
Lambda
我們可以看到:
{ v, insets ->
}
這就是 Kotlin Lambda。
前面學 Lambda 時可能覺得:
「Android 到底什麼時候會用?」
答案是:
非常常用。
Android 的 Listener、Callback 等程式大量使用 Lambda。
這段:
{ v, insets ->
有兩個參數:
v
與:
insets
其中 v 代表目前要處理的 View。
而:
insets
則包含系統視窗相關的 Insets 資訊。
我們接下來就是利用 insets 找出系統列所佔用的範圍。
程式:
val systemBars =
insets.getInsets(
WindowInsetsCompat.Type.systemBars()
)
這裡取得:
systemBars
也就是系統列相關的 Insets。
可以把它想像成系統告訴 App:
上面需要避開多少?
下面需要避開多少?
左邊需要避開多少?
右邊需要避開多少?
取得後可以使用:
systemBars.left
systemBars.top
systemBars.right
systemBars.bottom
接下來:
v.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
就是把剛剛取得的安全範圍設定成 View 的 Padding。
也就是:
top
↓
┌──────────────┐
│ │
left →│ View │← right
│ │
└──────────────┘
↑
bottom
對應程式:
v.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
如此一來,即使使用:
enableEdgeToEdge()
畫面內容也能根據系統列的實際範圍調整 Padding。
例如有人可能想:
v.setPadding(0, 50, 0, 50)
看起來好像也能避開上下區域。
但是 Android 裝置非常多。
不同手機可能有:
不同螢幕尺寸
不同狀態列高度
不同導覽方式
瀏海
挖孔螢幕
橫向模式
直向模式
手勢導覽
虛擬導覽按鍵
所以如果寫死:
50
不一定適用所有裝置。
使用:
WindowInsetsCompat
則可以依照目前裝置與系統狀態取得適合的 Insets。
Lambda 最後:
insets
Kotlin Lambda 的最後一個運算式可以作為回傳值。
例如以前學過:
val add = { a: Int, b: Int ->
a + b
}
最後:
a + b
就是回傳結果。
同樣的概念,這裡最後:
insets
就是 Lambda 的回傳值。
再次證明:
Kotlin 基礎語法到了 Android 開發後會一直重複出現。
現在重新看一次完整程式:
package com.example.myapplication
import android.os.Bundle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// 呼叫父類別 Activity 的初始化
super.onCreate(savedInstanceState)
// 啟用 Edge-to-Edge 顯示
enableEdgeToEdge()
// 載入 activity_main.xml 畫面
setContentView(R.layout.activity_main)
// 找到 id 為 main 的 View,
// 並監聽系統 WindowInsets
ViewCompat.setOnApplyWindowInsetsListener(
findViewById(R.id.main)
) { v, insets ->
// 取得系統列所占用的區域
val systemBars =
insets.getInsets(
WindowInsetsCompat.Type.systemBars()
)
// 根據系統列大小設定 View Padding,
// 避免內容被系統列遮住
v.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
// 回傳 Insets
insets
}
}
}
整體流程其實就是:
Android 建立 MainActivity
↓
執行 onCreate()
↓
super.onCreate()
↓
enableEdgeToEdge()
↓
setContentView()
↓
載入 activity_main.xml
↓
findViewById(R.id.main)
↓
取得 WindowInsets
↓
取得 systemBars
↓
設定 Padding
↓
畫面正確避開系統列
今天這個看似簡單的 MainActivity,其實已經大量使用前面幾天學過的 Kotlin。
例如:
| Kotlin 觀念 | Android 程式 |
|---|---|
| Class | class MainActivity |
| 繼承 | : AppCompatActivity() |
| Override | override fun onCreate() |
| Function | onCreate() |
| Nullable | Bundle? |
| val | val systemBars |
| Lambda | { v, insets -> } |
| 物件方法 | v.setPadding() |
所以前面的 Kotlin 基礎並不是獨立的章節。
真正進入 Android 之後,會發現:
Kotlin 基礎
+
物件導向
+
Lambda
+
Android Framework
=
Android App
今天不用急著把所有 Android API 背起來。
先記住幾個核心概念即可。
class MainActivity : AppCompatActivity()
代表 Android 的 Activity 類別。
override fun onCreate(savedInstanceState: Bundle?)
Activity 建立時的重要生命週期 Callback。
setContentView(R.layout.activity_main)
讓 Activity 使用指定 XML Layout。
findViewById(R.id.main)
透過 Resource ID 找到 XML 裡面的 View。
enableEdgeToEdge()
讓 App 內容延伸至螢幕邊緣。
WindowInsetsCompat.Type.systemBars()
取得系統列所占用的區域。
v.setPadding(
systemBars.left,
systemBars.top,
systemBars.right,
systemBars.bottom
)
讓畫面根據系統列位置調整,避免 UI 被遮住。
經過前面 Kotlin 基礎的練習,今天終於正式踏進 Android App。
從短短一個 MainActivity.kt,我們已經可以看到 Android 開發的幾個重要核心:
Activity
生命週期
XML Layout
Android Resource
View
Lambda Callback
Edge-to-Edge
WindowInsets
更重要的是,前面學過的 Kotlin:
Class
Inheritance
Override
Nullable
Lambda
val
Function
今天全部開始派上用場。
這也是我覺得學 Android 很有趣的地方:
前幾天看起來彼此獨立的 Kotlin 語法,開始在真正的 Android App 裡組合起來。
今天我們先知道:
MainActivity 如何被建立
↓
onCreate() 如何初始化
↓
XML 如何變成 App 畫面
↓
Android 如何處理系統列安全區域
接下來,就可以開始真正往 Android UI 前進,加入 TextView、Button、EditText 等 View,並讓 Kotlin 程式開始和使用者產生互動。
DAY 14,Kotlin 基礎課暫時告一段落,Android App 開發正式開始。
以上文章的程式與主軸是依你這次上傳的 MainActivity.kt 展開,尤其保留了原始程式中的 AppCompatActivity、onCreate()、enableEdgeToEdge()、setContentView()、ViewCompat 與 WindowInsetsCompat。
參考資料可搭配 Android Developers-Activity lifecycle 與 Android Developers-Edge-to-edge。