iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
Software Development

Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app系列 第 14

正式進入 Android!從 MainActivity 看懂 App 啟動與 Edge-to-Edge 畫面

  • 分享至 

  • xImage
  •  

我會以你上傳的 MainActivity(20260814-003651).kt 為主,內容重點是 Android Activity、生命週期的 onCreate()、XML Layout 載入、Edge-to-Edge 全螢幕顯示,以及 WindowInsets 系統列安全區域處理

開始實作-結束 Kotlin 基礎的語法,今天終於正式踏進 Android App世界

前面幾天我們花了不少時間學習 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() 有什麼用途?
  • 為什麼現在 Android 專案會看到 WindowInsets
  • Android 畫面如何避開狀態列與導覽列?

https://ithelp.ithome.com.tw/upload/images/20260814/20112100nTV2J8rXLq.png


1. 從 Kotlin Console 正式跨進 Android

學習 Kotlin 基礎時,我們可能會寫:

fun main() {
    println("Hello Android")
}

一般 Kotlin 程式可以從:

fun main()

開始執行。

但是 Android App 並不是單純執行一個 main() 函式。

Android 有自己的一套應用程式架構與生命週期管理方式。

例如使用者:

點擊 App
    ↓
Android 啟動應用程式
    ↓
建立 Activity
    ↓
執行 Activity 的生命週期
    ↓
顯示畫面

因此進入 Android 開發之後,我們首先要認識的就是:

Activity

2. Activity 是什麼?

可以先把 Activity 想成 Android App 裡的一個「畫面」。

例如:

登入畫面
LoginActivity

主畫面
MainActivity

設定畫面
SettingsActivity

商品詳細資料
ProductActivity

不同 Activity 可以負責不同的使用者操作畫面。

而 Android Studio 建立基本專案之後,通常會看到:

class MainActivity : AppCompatActivity()

這就是今天的主角。


3. 今天的 MainActivity

這次範例的完整程式如下:

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 畫面,就有這麼多東西?」

其實拆開來看並不複雜。


4. package:程式屬於哪一個套件

第一行:

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 專案整理程式碼的重要基礎。


5. import:把需要使用的 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

後面會逐一看到它們的用途。


6. MainActivity 繼承 AppCompatActivity

最重要的一行:

class MainActivity : AppCompatActivity()

如果前面已經學過 Kotlin Class 與繼承,這裡應該開始有熟悉的感覺了。

Kotlin 使用:

:

表示繼承。

也就是:

MainActivity
      ↓
繼承
      ↓
AppCompatActivity

所以:

class MainActivity : AppCompatActivity()

可以理解成:

MainActivity 是一個 Activity,而且繼承 AppCompatActivity 所提供的 Android Activity 功能。

這也正好把前面學過的 Kotlin 物件導向概念正式接到 Android。


7. Android Activity 有生命週期

Activity 並不是建立之後就一直存在。

Android 會根據使用者操作以及系統狀態,呼叫 Activity 不同的生命週期方法。

常見生命週期包括:

onCreate()
    ↓
onStart()
    ↓
onResume()
    ↓
使用者正在操作 App
    ↓
onPause()
    ↓
onStop()
    ↓
onDestroy()

可以簡化理解為:

建立
 ↓
顯示
 ↓
可以操作
 ↓
離開畫面
 ↓
停止
 ↓
銷毀

今天先從最重要的:

onCreate()

開始。


8. onCreate():Activity 建立時的重要初始化位置

程式中:

override fun onCreate(savedInstanceState: Bundle?) {

onCreate() 是 Activity 建立時的重要生命週期 Callback。

我們通常會在這裡進行畫面的初始設定。

例如:

設定 Layout
初始化 View
設定按鈕事件
建立資料
設定 RecyclerView
初始化 ViewModel

等等。

所以未來寫 Android 時,會非常常看到:

override fun onCreate(savedInstanceState: Bundle?) {

}

9. override 又出現了

前面學 Kotlin 物件導向時,我們學過:

override

現在 Android 馬上就用到了。

override fun onCreate(...)

代表:

MainActivity 要覆寫父類別 AppCompatActivity 所提供的 onCreate()。

也就是:

AppCompatActivity
       ↓
提供 onCreate()
       ↓
MainActivity
       ↓
override onCreate()

所以前面學習 Kotlin 的:

Class
Inheritance
Override

並不是只為了考語法。

進入 Android 之後,馬上就會大量使用。


10. savedInstanceState 為什麼有問號?

仔細看:

savedInstanceState: Bundle?

這裡又出現前面學過的 Kotlin Nullable。

Bundle?

最後面的:

?

代表:

Bundle 可以有值
也可以是 null

所以 Android 的程式碼其實就是把前面學習的 Kotlin 基礎不斷組合起來。

例如這一行已經包含:

override
fun
參數
型別
Nullable

這也是為什麼先學 Kotlin 基礎非常重要。


11. super.onCreate(savedInstanceState)

進入函式後第一行:

super.onCreate(savedInstanceState)

super 指的是父類別。

我們的 MainActivity:

class MainActivity : AppCompatActivity()

父類別就是:

AppCompatActivity

所以:

super.onCreate(savedInstanceState)

就是呼叫父類別的 onCreate()

可以簡單理解成:

先讓 AppCompatActivity
完成 Activity 本身需要的初始化

然後

MainActivity
再進行自己的設定

這也是 Android Activity 初始化的重要部分。


12. enableEdgeToEdge()

接下來:

enableEdgeToEdge()

這是現在 Android 專案很常看到的一行。

所謂:

Edge-to-Edge

就是讓 App 的內容可以延伸到螢幕邊緣以及系統列所在區域。

傳統畫面可能比較像:

┌─────────────────────┐
│ Status Bar          │
├─────────────────────┤
│                     │
│     App Content     │
│                     │
├─────────────────────┤
│ Navigation Bar      │
└─────────────────────┘

Edge-to-Edge 則可以讓內容延伸到更完整的螢幕區域:

┌─────────────────────┐
│                     │
│                     │
│     App Content     │
│                     │
│                     │
└─────────────────────┘

可以讓 App 畫面更符合現代 Android 的全螢幕設計方式。


13. 但 Edge-to-Edge 有一個問題

既然內容可以延伸到系統列區域,就可能發生:

文字被 Status Bar 擋住

Button 跑到 Navigation Bar 底下

UI 被系統操作區域覆蓋

例如:

┌─────────────────────┐
│ 12:30    Wi-Fi  🔋 │
│ 我的標題 ← 被壓住   │
│                     │
│                     │
│      Button         │
│_____________________│

所以只呼叫:

enableEdgeToEdge()

還不夠。

我們還需要知道:

系統列到底佔用了多少空間?

這就是接下來 WindowInsets 要處理的事情。


14. setContentView():把 XML Layout 顯示出來

接下來是 Android 初學最重要的一行之一:

setContentView(R.layout.activity_main)

它的意思可以先簡單理解成:

MainActivity 要使用 activity_main.xml 當作畫面。

Android 傳統 View 系統通常會把:

程式邏輯

與:

畫面 Layout

分開。

例如:

MainActivity.kt
      ↓
   程式邏輯

activity_main.xml
      ↓
   UI 畫面

然後透過:

setContentView(R.layout.activity_main)

將兩者連接起來。


15. 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
       ↓
顯示畫面

16. findViewById(R.id.main)

下一段:

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 程式互相連接的重要概念。


17. ViewCompat.setOnApplyWindowInsetsListener()

接著進入今天比較新的 Android UI 處理:

ViewCompat.setOnApplyWindowInsetsListener(
    findViewById(R.id.main)
) { v, insets ->

}

這段主要目的是取得系統視窗所占用的安全區域資訊。

這裡又出現前面學過的:

Lambda

我們可以看到:

{ v, insets ->

}

這就是 Kotlin Lambda。

前面學 Lambda 時可能覺得:

「Android 到底什麼時候會用?」

答案是:

非常常用。

Android 的 Listener、Callback 等程式大量使用 Lambda。


18. Lambda 裡面的 v 與 insets

這段:

{ v, insets ->

有兩個參數:

v

與:

insets

其中 v 代表目前要處理的 View。

而:

insets

則包含系統視窗相關的 Insets 資訊。

我們接下來就是利用 insets 找出系統列所佔用的範圍。


19. 取得 systemBars

程式:

val systemBars =
    insets.getInsets(
        WindowInsetsCompat.Type.systemBars()
    )

這裡取得:

systemBars

也就是系統列相關的 Insets。

可以把它想像成系統告訴 App:

上面需要避開多少?
下面需要避開多少?
左邊需要避開多少?
右邊需要避開多少?

取得後可以使用:

systemBars.left
systemBars.top
systemBars.right
systemBars.bottom

20. setPadding() 避開系統列

接下來:

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。


21. 為什麼不能自己寫死 Padding?

例如有人可能想:

v.setPadding(0, 50, 0, 50)

看起來好像也能避開上下區域。

但是 Android 裝置非常多。

不同手機可能有:

不同螢幕尺寸
不同狀態列高度
不同導覽方式
瀏海
挖孔螢幕
橫向模式
直向模式
手勢導覽
虛擬導覽按鍵

所以如果寫死:

50

不一定適用所有裝置。

使用:

WindowInsetsCompat

則可以依照目前裝置與系統狀態取得適合的 Insets。


22. 最後為什麼要寫 insets?

Lambda 最後:

insets

Kotlin Lambda 的最後一個運算式可以作為回傳值。

例如以前學過:

val add = { a: Int, b: Int ->
    a + b
}

最後:

a + b

就是回傳結果。

同樣的概念,這裡最後:

insets

就是 Lambda 的回傳值。

再次證明:

Kotlin 基礎語法到了 Android 開發後會一直重複出現。


23. 把整個 MainActivity 流程串起來

現在重新看一次完整程式:

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
          ↓
畫面正確避開系統列

24. 前面學的 Kotlin 終於全部串起來了

今天這個看似簡單的 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

25. 今天最重要的觀念

今天不用急著把所有 Android API 背起來。

先記住幾個核心概念即可。

MainActivity

class MainActivity : AppCompatActivity()

代表 Android 的 Activity 類別。

onCreate()

override fun onCreate(savedInstanceState: Bundle?)

Activity 建立時的重要生命週期 Callback。

載入 XML Layout

setContentView(R.layout.activity_main)

讓 Activity 使用指定 XML Layout。

找到 View

findViewById(R.id.main)

透過 Resource ID 找到 XML 裡面的 View。

Edge-to-Edge

enableEdgeToEdge()

讓 App 內容延伸至螢幕邊緣。

WindowInsets

WindowInsetsCompat.Type.systemBars()

取得系統列所占用的區域。

安全 Padding

v.setPadding(
    systemBars.left,
    systemBars.top,
    systemBars.right,
    systemBars.bottom
)

讓畫面根據系統列位置調整,避免 UI 被遮住。


26. DAY 14 小結

經過前面 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 前進,加入 TextViewButtonEditText 等 View,並讓 Kotlin 程式開始和使用者產生互動。

DAY 14,Kotlin 基礎課暫時告一段落,Android App 開發正式開始。

以上文章的程式與主軸是依你這次上傳的 MainActivity.kt 展開,尤其保留了原始程式中的 AppCompatActivityonCreate()enableEdgeToEdge()setContentView()ViewCompatWindowInsetsCompat

參考資料可搭配 Android Developers-Activity lifecycleAndroid Developers-Edge-to-edge


上一篇
Kotlin 物件導向進階——抽象類別、介面、Inner Class 與 Object
系列文
Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app14
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言