iT邦幫忙

2026 iThome 鐵人賽

DAY 19
0
Software Development

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

Intent 不只會跳頁:Activity 資料傳遞、回傳結果與多國語系實戰

  • 分享至 

  • xImage
  •  

Day 19|Intent 不只會跳頁:Activity 資料傳遞、回傳結果與多國語系實戰

前幾天開始接觸 Android 的 Activity 之後,今天要把 Intent 再往前推一步。

Intent 不只是拿來「從 A 畫面跳到 B 畫面」,它還可以:

  • Activity 之間傳送資料
  • 將第二個 Activity 的資料回傳給第一個 Activity
  • 搭配 BundleputExtra() 傳送多筆資料
  • 使用 ActivityResult 接收其他 Activity 的執行結果
  • 搭配 strings.xml 建立多國語系
  • 將畫面文字與程式邏輯分離

今天的練習主要可以分成三個部分:

Intent_3:城市圖片與資料傳遞

Intent_4:Activity 1 / Activity 2 雙向資料交換

Intent_5:飲料點餐 + Activity Result + 多國語系


一、Intent 到底是什麼?

在 Android 中,可以把 Intent 想成 Activity 之間傳遞的「信封」。

例如:

MainActivity
    │
    │ Intent
    │ putExtra()
    ▼
SecondActivity
    │
    │ setResult()
    ▼
MainActivity

我們不只可以告訴 Android:

我要開啟哪一個 Activity。

還可以順便在 Intent 裡面放資料。

例如:

val intent = Intent(this, SecondActivity::class.java)

intent.putExtra("name", "Alex")

startActivity(intent)

到了 SecondActivity 再透過:

val name = intent.getStringExtra("name")

把資料取出來。

這就是今天所有範例最核心的概念。

參考資料:Android Developers — Intent
https://developer.android.com/guide/components/intents-filters


二、Intent_3:使用 Intent 傳遞城市資料

第一個練習是城市選擇。

MainActivity 裡面建立四個 ImageButton

val btnTaipei: ImageButton = findViewById(R.id.btnTaipei)
val btnNewTaipei: ImageButton = findViewById(R.id.btnNewTaipei)
val btnTaoyuan: ImageButton = findViewById(R.id.btnTaoyuan)
val btnHsinchu: ImageButton = findViewById(R.id.btnHsinchu)

分別代表:

台北
新北
桃園
新竹

接著替每一個按鈕建立 setOnClickListener

例如台北:

btnTaipei.setOnClickListener {
    startDisplayActivity(
        getString(R.string.taipei),
        R.drawable.taipei,
        R.string.taipei_desc
    )
}

其他城市也是使用相同的方法:

btnNewTaipei.setOnClickListener {
    startDisplayActivity(
        getString(R.string.new_taipei),
        R.drawable.newtaipei,
        R.string.new_taipei_desc
    )
}

btnTaoyuan.setOnClickListener {
    startDisplayActivity(
        getString(R.string.taoyuan),
        R.drawable.taoyuan,
        R.string.taoyuan_desc
    )
}

btnHsinchu.setOnClickListener {
    startDisplayActivity(
        getString(R.string.hsinchu),
        R.drawable.hsinchu,
        R.string.hsinchu_desc
    )
}

這裡沒有每一個按鈕都重新寫一次 Intent,而是整理成:

private fun startDisplayActivity(
    cityName: String,
    imageResId: Int,
    descResId: Int
) {
    val intent = Intent(this, DisplayActivity::class.java).apply {

        // 傳送城市名稱
        putExtra("CITY_NAME", cityName)

        // 傳送圖片 Resource ID
        putExtra("IMAGE_RES_ID", imageResId)

        // 傳送城市說明文字 Resource ID
        putExtra("DESC_RES_ID", descResId)
    }

    startActivity(intent)
}

這段程式一次傳送了三筆資料:

CITY_NAME
IMAGE_RES_ID
DESC_RES_ID

也就是:

MainActivity
       │
       │ CITY_NAME
       │ IMAGE_RES_ID
       │ DESC_RES_ID
       ▼
DisplayActivity

這也是函式封裝的好處。如果未來再增加「台中」、「台南」、「高雄」,不需要重複撰寫整段 Intent 程式。實際上傳的 MainActivity.kt 正是透過 startDisplayActivity() 統一將城市名稱、圖片 Resource ID 與說明 Resource ID 傳入 DisplayActivity

參考資料:Android Developers — Intent
https://developer.android.com/reference/android/content/Intent


三、DisplayActivity 接收 Intent 資料

資料送出去之後,接下來就是要把它接回來。

DisplayActivity

val cityName = intent.getStringExtra("CITY_NAME")

val imageResId = intent.getIntExtra(
    "IMAGE_RES_ID",
    0
)

val descriptionResId = intent.getIntExtra(
    "DESC_RES_ID",
    0
)

這裡要注意一件非常重要的事情:

傳送與接收使用的 Key 必須完全相同。

例如:

putExtra("CITY_NAME", ...)
               ↓
getStringExtra("CITY_NAME")

如果寫成:

getStringExtra("city_name")

就無法取得原本的資料。

取得圖片 Resource ID 後:

if (imageResId != 0) {
    imageView.setImageResource(imageResId)
}

取得文字 Resource ID 後:

if (descriptionResId != 0) {
    textView.setText(descriptionResId)
}

另外也將城市名稱設定到 ActionBar:

supportActionBar?.apply {
    title = cityName
    setDisplayHomeAsUpEnabled(true)
}

並加入返回功能:

override fun onSupportNavigateUp(): Boolean {
    onBackPressedDispatcher.onBackPressed()
    return true
}

因此這個 Activity 已經不是單純跳頁,而是會根據 MainActivity 傳入的資料,動態改變:

標題
圖片
說明文字

實際程式就是從 Intent 取得 CITY_NAMEIMAGE_RES_IDDESC_RES_ID,再分別設定 ActionBar、ImageView 與 TextView。

參考資料:Android Developers — Intent.getStringExtra
https://developer.android.com/reference/android/content/Intent#getStringExtra(java.lang.String)


四、Intent_4:資料不只傳出去,還要傳回來

第二個練習開始更有意思。

程式架構變成:

              ┌──────────────┐
              │  Activity_1  │
              └──────┬───────┘
                     │
                     │ Result
                     ▼
MainActivity ◄───────────────
     │
     │
     ▼
              ┌──────────────┐
              │  Activity_2  │
              └──────┬───────┘
                     │
                     │ Result
                     ▼
MainActivity ◄───────────────

也就是 MainActivity 可以進入不同 Activity。

Activity 執行完成之後,又可以把結果傳回 MainActivity。


五、Activity Result API

在 MainActivity 中首先定義:

private val Act1Code: Int = 100
private val ACT2_Code: Int = 200

接著:

private lateinit var getLaunch: ActivityResultLauncher<Intent>

然後在 onCreate() 中註冊:

getLaunch = registerForActivityResult(
    ActivityResultContracts.StartActivityForResult(),
    object : ActivityResultCallback<ActivityResult> {

        override fun onActivityResult(result: ActivityResult) {

            Log.d("main", "get callback")

            val code = result.resultCode

            Log.d("main", "code = $code")

            if (code == Act1Code) {

                val act1Intent = result.data

                val data =
                    act1Intent?.getStringExtra("act1_data")

                textViewData.text = data
            }

            if (code == ACT2_Code) {

                val act2Intent = result.data

                val data =
                    act2Intent?.getStringExtra("act2_data")

                textViewData.text = data
            }
        }
    }
)

這裡是今天非常重要的一段。

registerForActivityResult() 可以事先註冊:

當其他 Activity 回傳結果時,我要怎麼處理。

實際上傳的 Intent_4 MainActivity 使用 ActivityResultContracts.StartActivityForResult() 註冊 launcher,並依 resultCode 為 100 或 200 判斷結果來自哪一個 Activity。

參考資料:Android Developers — Activity Result API
https://developer.android.com/training/basics/intents/result


六、MainActivity 傳資料給 Activity_1

點擊第一個按鈕:

buttonAct1.setOnClickListener {

    val data =
        if (editTextInput1.text.isEmpty()) {
            "no data"
        } else {
            editTextInput1.text.toString()
        }

    val intent =
        Intent(
            this@MainActivity,
            Activity_1::class.java
        )

    intent.putExtra(
        "data",
        data
    )

    getLaunch.launch(intent)
}

流程:

EditText
   │
   ▼
取得輸入文字
   │
   ▼
Intent.putExtra("data", data)
   │
   ▼
getLaunch.launch(intent)
   │
   ▼
Activity_1

如果 EditText 沒有輸入資料:

"no data"

避免傳送空字串。

第二個 Activity 同樣如此:

buttonAct2.setOnClickListener {

    val data =
        if (editTextInput2.text.isEmpty()) {
            "no data"
        } else {
            editTextInput2.text.toString()
        }

    val intent =
        Intent(
            this@MainActivity,
            Activity_2::class.java
        )

    intent.putExtra(
        "data",
        data
    )

    getLaunch.launch(intent)
}

這兩個按鈕都使用同一個 ActivityResultLauncher 啟動不同 Activity,是這個範例值得注意的地方。

參考資料:Android Developers — Activity Result
https://developer.android.com/training/basics/intents/result


七、Activity_1 接收資料,再回傳結果

Activity_1 首先取得 MainActivity 傳過來的:

val data = intent.getStringExtra("data")

textViewData.text = data

接下來使用者可以在:

editTextInput

輸入新的資料。

按下返回按鈕:

buttonBack.setOnClickListener {

    val resultIntent = Intent()

    resultIntent.putExtra(
        "act1_data",
        editTextInput.text.toString()
    )

    setResult(
        Act1Code,
        resultIntent
    )

    finish()
}

流程變成:

MainActivity
    │
    │ "data"
    ▼
Activity_1
    │
    │ 使用者輸入資料
    │
    │ "act1_data"
    ▼
setResult(100, resultIntent)
    │
    ▼
finish()
    │
    ▼
MainActivity callback

這就是 Activity 之間的雙向資料傳遞。上傳的 Activity_1.kt 使用 "data" 接收 MainActivity 資料,再以 "act1_data" 搭配結果碼 100 回傳。

參考資料:Android Developers — setResult
https://developer.android.com/reference/android/app/Activity#setResult(int,%20android.content.Intent)


八、Activity_2 使用相同概念

Activity_2:

val data = intent.getStringExtra("data")
textViewData.text = data

回傳時:

buttonBack.setOnClickListener {

    val resultIntent = Intent()

    resultIntent.putExtra(
        "act2_data",
        editTextInput.text.toString()
    )

    setResult(
        ACT2_Code,
        resultIntent
    )

    finish()
}

差異主要在:

Activity_1 → act1_data → ResultCode 100
Activity_2 → act2_data → ResultCode 200

因此 MainActivity 就能知道:

這筆資料到底是哪一個 Activity 回傳的?

實際 Activity_2.kt 也是相同結構,只是使用 "act2_data" 與結果碼 200。

參考資料:Android Developers — Activity
https://developer.android.com/reference/android/app/Activity


九、Intent_5:實作飲料點餐

接下來把 Intent 應用到更接近實際 App 的情境:

飲料點餐系統。

MainActivity 顯示:

飲料:
甜度:
冰塊:

按下選擇後:

btnSelect.setOnClickListener {

    val intent =
        Intent(
            this,
            SecondActivity::class.java
        )

    startActivityForResult(
        intent,
        ACTCODE
    )
}

接著進入 SecondActivity 選擇:

飲料名稱
甜度
冰塊

上傳的 MainActivity 目前實際使用的是傳統的 startActivityForResult(intent, ACTCODE);檔案中也保留了一段註解掉的新版 registerForActivityResult() 寫法,可以直接拿來比較兩種 Activity Result 寫法。

參考資料:Android Developers — Getting a result from an activity
https://developer.android.com/training/basics/intents/result


十、SecondActivity:RadioGroup 取得甜度與冰塊

SecondActivity 中:

val etDrink =
    findViewById<EditText>(
        R.id.et_drink
    )

val rgSugar =
    findViewById<RadioGroup>(
        R.id.rg_sugar
    )

val rgIce =
    findViewById<RadioGroup>(
        R.id.rg_ice
    )

val btnSend =
    findViewById<Button>(
        R.id.btn_send
    )

按下送出:

btnSend.setOnClickListener {

    val drink =
        etDrink.text.toString()

    val sugar =
        findViewById<RadioButton>(
            rgSugar.checkedRadioButtonId
        ).text.toString()

    val ice =
        findViewById<RadioButton>(
            rgIce.checkedRadioButtonId
        ).text.toString()

    val intent = Intent()

    intent.putExtra(
        "drink",
        drink
    )

    intent.putExtra(
        "sugar",
        sugar
    )

    intent.putExtra(
        "ice",
        ice
    )

    setResult(
        RESULT_OK,
        intent
    )

    finish()
}

這次一次回傳三筆資料:

drink
sugar
ice

流程:

SecondActivity

EditText ──────► drink
RadioGroup ────► sugar
RadioGroup ────► ice
                   │
                   ▼
              Intent
                   │
          putExtra × 3
                   │
                   ▼
          setResult(RESULT_OK)
                   │
                   ▼
                finish()
                   │
                   ▼
             MainActivity

實際 SecondActivity.kt 就是讀取 EditText 與兩組 RadioGroup 的選項,再用三個 Extra 回傳。

參考資料:Android Developers — RadioGroup
https://developer.android.com/reference/android/widget/RadioGroup


十一、MainActivity 接收飲料訂單

回到 MainActivity:

override fun onActivityResult(
    requestCode: Int,
    resultCode: Int,
    data: Intent?
) {

    super.onActivityResult(
        requestCode,
        resultCode,
        data
    )

    if (
        requestCode == ACTCODE &&
        resultCode == RESULT_OK
    ) {

        data?.let {

            tvDrink.text =
                getString(
                    R.string.drink_label,
                    it.getStringExtra("drink")
                )

            tvSugar.text =
                getString(
                    R.string.sugar_label,
                    it.getStringExtra("sugar")
                )

            tvIce.text =
                getString(
                    R.string.ice_label,
                    it.getStringExtra("ice")
                )
        }
    }
}

這裡除了 Intent 之外,又出現今天另外一個重要觀念:

getString(
    R.string.drink_label,
    it.getStringExtra("drink")
)

而不是直接:

tvDrink.text =
    "飲料:" + it.getStringExtra("drink")

為什麼?

因為這樣才能把顯示文字交給:

strings.xml

管理,替多國語系做好準備。上傳程式的確透過 getString(R.string.drink_label, ...)sugar_labelice_label 組合回傳內容。

參考資料:Android Developers — String resources
https://developer.android.com/guide/topics/resources/string-resource


十二、今天另一個重點:strings.xml 多國語系

今天 Android Studio 的畫面中,可以看到 Translations Editor

Android Studio Translations Editor

這個工具可以直接管理不同語言的 String Resource。

畫面中可以看到像:

Key             Default Value       Chinese

app_name        Intent_5            Intent_5
order           Order drink         點餐:
select          Select              選擇
drink           Drink name          飲料名稱
sugar           Sugar level         甜度:
ice             Ice level           冰塊:
nosugar         No sugar            無糖
littlesugar     Little sugar        微糖
halfsugar       Half sugar          半糖
fullsugar       Full sugar          全糖
noice           No ice              去冰
littleice       Little ice          微冰
halfice         Half ice            少冰
normalice       Normal ice          正常冰
send            Send                送出
confirm_order   Confirm Order       確認餐點:
drink_label     Drink:              飲料:
sugar_label     Sugar:              甜度:
ice_label       Ice:                冰塊:

這就是 Android Resource System 很方便的地方。

程式不用自己判斷:

if (language == "zh") {
    text = "飲料"
} else {
    text = "Drink"
}

而是直接寫:

android:text="@string/drink"

Android 就會依據裝置目前的語言,自動選擇適合的資源。

參考資料:Android Developers — Localize your app
https://developer.android.com/guide/topics/resources/localization


十三、strings.xml 的基本概念

預設語系通常放:

res/
└── values/
    └── strings.xml

例如:

<resources>

    <string name="app_name">
        Intent_5
    </string>

    <string name="order">
        Order drink
    </string>

    <string name="select">
        Select
    </string>

    <string name="drink">
        Drink name
    </string>

    <string name="sugar">
        Sugar level
    </string>

    <string name="ice">
        Ice level
    </string>

    <string name="nosugar">
        No sugar
    </string>

    <string name="littlesugar">
        Little sugar
    </string>

    <string name="halfsugar">
        Half sugar
    </string>

    <string name="fullsugar">
        Full sugar
    </string>

    <string name="noice">
        No ice
    </string>

    <string name="littleice">
        Little ice
    </string>

    <string name="halfice">
        Half ice
    </string>

    <string name="normalice">
        Normal ice
    </string>

    <string name="send">
        Send
    </string>

</resources>

繁體中文則可以建立對應的語系資源目錄,例如:

res/
├── values/
│   └── strings.xml
│
└── values-zh-rTW/
    └── strings.xml

繁體中文:

<resources>

    <string name="app_name">
        Intent_5
    </string>

    <string name="order">
        點餐:
    </string>

    <string name="select">
        選擇
    </string>

    <string name="drink">
        飲料名稱
    </string>

    <string name="sugar">
        甜度:
    </string>

    <string name="ice">
        冰塊:
    </string>

    <string name="nosugar">
        無糖
    </string>

    <string name="littlesugar">
        微糖
    </string>

    <string name="halfsugar">
        半糖
    </string>

    <string name="fullsugar">
        全糖
    </string>

    <string name="noice">
        去冰
    </string>

    <string name="littleice">
        微冰
    </string>

    <string name="halfice">
        少冰
    </string>

    <string name="normalice">
        正常冰
    </string>

    <string name="send">
        送出
    </string>

</resources>

Android 會依照系統 Locale 自動尋找適合的 Resource。

參考資料:Android Developers — Providing resources
https://developer.android.com/guide/topics/resources/provid

📂 Day 19 範例程式

🔹 Intent_3:Intent 傳遞城市名稱、圖片與說明

📱 Intent_3 完整專案

主要練習:

  • Intent
  • putExtra()
  • getStringExtra()
  • getIntExtra()
  • Activity 頁面切換
  • 傳遞圖片 Resource ID
  • 傳遞 String Resource ID

🔹 Intent_4:Activity 雙向資料傳遞

📱 Intent_4 完整專案

主要練習:

  • Intent.putExtra()
  • registerForActivityResult()
  • ActivityResultLauncher
  • ActivityResultContracts.StartActivityForResult()
  • setResult()
  • Activity 之間雙向傳遞資料
  • MainActivity → Activity_1 → MainActivity
  • MainActivity → Activity_2 → MainActivity

🔹 Intent_5:飲料點餐+Activity Result+多國語系

🥤 Intent_5 完整專案

主要練習:

  • Intent
  • startActivityForResult()
  • onActivityResult()
  • setResult()
  • EditText
  • RadioGroup
  • RadioButton
  • 飲料、甜度、冰塊資料回傳
  • strings.xml
  • Android 多國語系

🖼️ Android Studio 多國語系 Translations Editor

Android Studio Translations Editor

透過 Android Studio 的 Translations Editor,可以集中管理不同語言的 strings.xml

例如:

Key English 繁體中文
order Order drink 點餐
select Select 選擇
drink Drink name 飲料名稱
sugar Sugar level 甜度
ice Ice level 冰塊
nosugar No sugar 無糖
littlesugar Little sugar 微糖
halfsugar Half sugar 半糖
fullsugar Full sugar 全糖
noice No ice 去冰
littleice Little ice 微冰
halfice Half ice 少冰
normalice Normal ice 正常冰

https://ithelp.ithome.com.tw/upload/images/20260819/20112100Dk3jhpBG9g.png


上一篇
Android 學生資料表單:RadioButton、CheckBox 與表單互動實作
下一篇
Android Activity 資料傳遞實戰:從飲料點餐到會員註冊,掌握 Intent 雙向回傳
系列文
Android 鐵人賽: 天命最高 - 陪伴大家一步步打造屬於自己的app21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言