目前為止,您使用的應用程式只執行一項
活動(activity)
。但實際上很多 Android 應用程式需要執行多項活動(activity),並透過導覽(navigation)
來切換應用程式。
學習目標
- 使用
明確意圖(intent)
導覽(navigation)至特定活動(activity)。 - 使用
隱含意圖(intent)
導覽(navigation)至其他應用程式的內容。 - 新增
選單(menu)
選項,並新增按鈕至應用程式列(app bar)
。
在本程式碼研究室中,您要建構一個字典應用程式,讓應用程式使用多項活動,並使用意圖(intent)
切換應用程式,同時傳遞資料至其他應用程式。
範例程式碼
在後續步驟中,您要使用 Words 應用程式。Words 應用程式是簡單的字典應用程式,包含字母清單、每個字母的字詞,以及在瀏覽器中查詢個別字詞定義的功能。



下載範例程式碼
請注意,GitHub 下載範例程式碼的資料夾名稱是 android-basics-kotlin-words-app-starter
。在 Android Studio 中開啟專案時,請選取這個資料夾。
在 Android Studio 中開啟專案
- 啟動 Android Studio。
- 開啟
android-basics-kotlin-words-app-starter
專案 - 按一下「Run」按鈕,確認應用程式的建構符合預期。
Word 應用程式總覽
繼續操作前,請花點時間熟悉專案內容。您必須熟悉上一個單元的所有概念。應用程式目前有兩項活動(activities)
,包含 recycler view
和 adapter
。

具體而言,您會使用下列檔案:
MainActivity
的RecyclerView
會使用LetterAdapter
。每個字母都是包含onClickListener
的按鈕,這些按鈕目前沒有任何內容。您可以在這裡管理按鈕點按操作以導覽至DetailActivity
。DetailActivity
的RecyclerView
使用WordAdapter
,以顯示字詞清單(list)。雖然您暫時無法前往此畫面,但請記得每個字詞都有對應的按鈕和onClickListener
。在這個步驟中,為了導覽至瀏覽器並顯示字詞的定義,您要加入程式碼。MainActivity
也需要進行一些變更。在這個步驟中,為了顯示按鈕,讓使用者切換清單(list)和格線版面配置,您要實作選項選單(option menu)。

Intent簡介
您已完成初始專案設定,接著我們要探討意圖(intent),以及如何在應用程式中使用意圖(intent)。
意圖(intent)
是物件,代表要執行的一些動作。我們最常看到意圖用來啟動活動(activity)
(當然意圖不只這個用途)。意圖(intent)
分為兩種類型:隱含(implicit)
和明確(explicit)
。
- 明確意圖(explicit intent) :極為精確,您確切知道要啟動的活動(activity) (通常是應用程式的畫面)。
- 隱含意圖(implicit intent) :較抽象,系統收到動作的類型 (例如開啟連結、撰寫電子郵件或撥打電話),然後負責判斷如何完成要求。
您可能已看過這兩種意圖(intent),只是未察覺到。一般而言,在您的應用程式中 顯示活動(activity) 時,即是使用 明確意圖(explicit intent) 。


但如果動作不涉及目前的應用程式 (例如您找到有趣的 Android 文件資訊頁面,並想分享給朋友),即使用 隱含意圖(implicit intent) 。您可能會看到類似選單,詢問您要使用什麼應用程式分享資訊頁面。


設定 Explicit Intent
現在可以實作第一個意圖(Intent)了。在第一個畫面上,當使用者輕觸字母後,就會前往列有字詞清單的第二個畫面。因為已實作 DetailActivity
,所以只需使用Intent啟動此動作。因為應用程式已經知道要啟動特定的活動(Activity),您可以使用明確意圖(Explicit Intent)。
建立並使用Intent只需幾個步驟:
- 開啟
LetterAdapter.kt
並向下捲動至onBindViewHolder()
。在這一行程式碼下,設定按鈕文字,並設定holder.button
的onClickListener
。
1 | holder.button.setOnClickListener { |
- 然後取得
context
的參考。
1 | val context = holder.itemView.context |
- 建立
Intent
,並傳入目的地活動的結構定義和 class name。
1 | val intent = Intent(context, DetailActivity::class.java) |
- 您要顯示的活動名稱指定為
DetailActivity::class.java
。實際的DetailActivity
物件會在幕後建立。
- 呼叫
putExtra
方法,然後傳入「letter」做為第一個引數,按鈕文字則做為第二個引數。
1 | intent.putExtra("letter", holder.button.text.toString()) |
extra 是什麼?請記得,intent 只是一組操作說明,但目的地動作目前沒有 intent。而 extra 是一段資料,例如數字或字串,即之後擷取的指定名稱。這類似於呼叫函式時傳遞引數。因為 DetailActivity
可顯示任何字母,您必須指定顯示哪個字母。
此外,您認為為什麼需要呼叫 toString()
?按鈕的文字是字串,對吧?
可以這麼說。它其實是 CharSequence
類型,即所謂的介面。您目前不需瞭解 Kotlin 介面的任何資訊,只需知道介面是用於確定字串等類型,以及實作特定函式和屬性的方式。您可以將 - 聯想為類似字串 class 的一般表示法。按鈕的 text
屬性可以是字串,或同時是 CharSequence
的任何物件。但 putExtra()
方法接受 String
,不是 CharSequence
,所以必須呼叫 toString()
。
- 呼叫結構定義的
startActivity()
方法,並傳入intent
。
1 | context.startActivity(intent) |
接著執行應用程式,並嘗試輕觸字母。隨即顯示詳細資料畫面!但無論使用者輕觸哪個字母,詳細資料畫面會一律顯示字母 A 的字詞。在詳細資料動作中,一些工作仍有待完成,才會顯示傳遞字母的字詞,作為 intent
額外資料。
設定 DetailActivity
您已建立第一個明確意圖!現在進入詳細資料畫面。
在 DetailActivity
的 onCreate
方法中,呼叫 setContentView
後,以程式碼取代硬式編碼字母,從 intent
取得傳入的 letterId
。
1 | val letterId = intent?.extras?.getString("letter").toString() |
這裡有很多要注意的事項,所以接著我們要查看每個項目:
首先,intent
屬性的來源為何?這不是 DetailActivity
的屬性,而是任何活動的屬性。此屬性會持續參考啟動活動使用的意圖。
額外屬性是 Bundle
類型,或許您已猜到,該屬性提供方法來存取傳入意圖的所有額外屬性。
這兩個屬性會以問號標示。原因是什麼呢?原因是 intent
和 extras
屬性可為空值,換句話說,您可以使用值,也可以不使用值。有時,您可能想要變數為 null
。intent
屬性可能不是 Intent
(如果活動不是從意圖啟動),此外,額外的屬性可能不是 Bundle
,而是名為 null
的值。在 Kotlin 中,null
代表沒有值。物件可能存在,或可能是 null
。如果您的應用程式嘗試在 null
物件上存取屬性或呼叫函式,該應用程式就會異常終止。若要安全存取這個值,您必須在名稱後方加上 ?
。如果 intent
是 null
,應用程式不會嘗試存取額外的屬性,此外,如果 extras
為空值,程式碼也不會嘗試呼叫 getString()
。
如何知道哪些屬性需要問號才能確保空值的安全性?您可以透過類型名稱後方是否有問號或驚嘆號來判斷。

最後請注意,使用 getString
擷取實際字母會傳回 String?
,所以呼叫 toString()
可確保它是 String
,而不是 null
。
您現在執行應用程式,並導覽至詳細資料畫面時,應該會看到每個字母的字詞清單。
清除(Cleaning Up)
兩個程式碼會執行意圖,並擷取選取 extra
名稱「letter」(字母) 的字母硬式編碼。雖然小型樣本可以使用這方法,但對於大型應用程式 (其中包含需持續追蹤的大量意圖額外資料) 就不是最佳做法。
雖然您可以只建立名為「letter」的常數,但應用程式加入較多意圖額外資訊後,就不適合使用常數;更何況常數要放置在哪個 class 也是個問題。請記得,字串會同時用於 DetailActivity
和 MainActivity
。您必須定義常數,才可以跨多個 class 使用,並維持程式碼井然有序。
值得慶幸的是,我們可透過一項實用的 Kotlin 功能來區隔常數,而且不必使用名為「companion object」 class 的特定 instance,一樣可以使用常數。Companion object 類似於 class instance 這種其他物件。但在程式執行期間,companion object 只會存在一個 instance,所以有時稱為「單例模式(singleton)」。除了本程式碼研究室的適用範圍外,單例模式(singleton)仍有許多用途,但目前您要使用 companion object 規劃常數,並使其可從 DetailActivity
外部存取。您可以開始使用 companion object,重構「letter」(字母) 額外資料的程式碼。
- 在
onCreate
上方的DetailActivity
中,新增以下內容:
1 | companion object { |
- 請注意,這做法類似於定義 class,只是使用了
object
關鍵字。另外還有關鍵字companion
,表示該關鍵字與DetailActivity
class 相關聯,所以我們不必為其指定額外的類型名稱。
- 在大括號中加入字母常數的屬性。
1 | const val LETTER = "letter" |
- 若要使用新的常數,請更新
onCreate()
中呼叫的硬式編碼字母,如下所示:
1 | val letterId = intent?.extras?.getString(LETTER).toString() |
再次提醒您,常數通常會參考點標記法,但仍屬於 DetailActivity
。
- 切換至
LetterAdapter
,並修改呼叫putExtra
,以使用新的常數。
1 | intent.putExtra(DetailActivity.LETTER, holder.button.text.toString()) |
大功告成!重構後,您的程式碼會更容易閱讀和維護。如果您要變更程式碼,或新增的其他常數,只需在一個位置進行變更。
若要深入瞭解 companion object,請參閱物件運算式和宣告的 Kotlin 說明文件。
設定 Implicit Intent
在多數情況下,您的應用程式會顯示特定的活動(Activity)。但在部分情況下,您不會知道要啟動什麼活動(Activity)或應用程式(Application)。在我們的詳細資料畫面上,每個字詞按鈕會顯示使用者的字詞定義。
例如,使用 Google
搜尋提供的字典功能。您不是在應用程式中加入新活動,而是啟動裝置瀏覽器,顯示搜尋網頁。
所以您可能需要意圖(Intent)以在 Chrome
(Android 預設的瀏覽器) 中載入資訊頁面嗎?
答錯了。
部分使用者可能慣用第三方瀏覽器,或手機隨附製造商預先安裝的瀏覽器。他們也許已安裝 Google 搜尋應用程式,或是第三方字典應用程式。
您無法得知使用者安裝哪些應用程式,也無法假定他們要查詢的字詞。這範例正適合使用隱含意圖(Implicit Intent)。您的應用程式會提供系統採用動作的資訊,然後系統會判斷處置動作的方式,並在必要時提示使用者其他資訊。
請按照下列步驟建立隱含意圖:
- 在這個應用程式中,您要執行 Google 搜尋字詞。第一個搜尋結果是字詞的字典定義。由於每次搜尋都會使用相同的基準網址,因此建議您將網址定義為常數。在
DetailActivity
中修改隨附物件(companion object),增加新的常數SEARCH_PREFIX
。這是 Google 搜尋的基準網址。
1 | companion object { |
- 接著,開啟
WordAdapter
,然後在onBindViewHolder()
方法中呼叫按鈕的setOnClickListener()
。開始建立搜尋查詢的Uri
。呼叫parse()
以從String
建立Uri
時,您必須使用字串格式,才能將字詞附加至SEARCH_PREFIX
。
1 | holder.button.setOnClickListener { |
如果您想知道「URI」是什麼,URI 不是錯字,而是「統一資源識別項」。您可能已經知道網址,或「統一資源定位器」是指向網頁的字串。URI 是格式的一般用語。所有網址 (URL) 都是 URI,但不是所有 URI 都是網址。其他 URI (例如電話號碼位址) 會以 tel:
開頭,但系統會視為 URN 或統一資源名稱,而不是網址。用來代表兩者的資料類型稱為 URI
。

請注意,以下沒有任何與應用程式相關的活動。您只需提供 URI,但不知道最終用法。
- 定義
queryUrl
後,請將新的intent
物件初始化:
1 | val intent = Intent(Intent.ACTION_VIEW, queryUrl) |
但不必傳入結構定義和活動,而是傳入 Intent.ACTION_VIEW
和 URI
。
ACTION_VIEW
是通用意圖,可採用 URI,在本案例即是網址。接著,系統會在使用者的網路瀏覽器中,開啟 URI 處理意圖。其他意圖類型:
- CATEGORY_APP_MAPS:啟動地圖應用程式
- CATEGORY_APP_EMAIL:啟動電子郵件應用程式
- CATEGORY_APP_GALLERY:啟動圖片庫 (相簿) 應用程式
- ACTION_SET_ALARM:在背景設定鬧鐘
- ACTION_DIAL:撥打電話
若要瞭解詳情,請參閱部分常用意圖的說明文件。
- 最後,即使您不在應用程式中啟動任何特定活動,但您仍會呼叫
startActivity()
並傳入intent
,指示系統啟動其他應用程式。
1 | context.startActivity(intent) |
現在當您啟動應用程式、前往字詞清單,並輕觸其中一個字詞時,您的裝置應會導覽至該網址 (或根據安裝的應用程式,顯示選項清單)。
設定 Menu 和 Icons
新增明確和隱含意圖,更方便瀏覽應用程式後,您可以新增選單選項,讓使用者可以在字母和清單與格線版面配置間切換。


您目前可能看到很多應用程式畫面的頂端,使用此選項列。這是應用程式列,除了顯示應用程式名稱外,應用程式列也可以自訂並代管許多實用的功能,例如實用動作的快速鍵或溢位選單。

在本應用程式中,我們不會新增完備的選單,您會瞭解如何在應用程式列中新增自訂按鈕,方便使用者變更版面配置。
- 首先,您必須匯入兩個圖示,代表格狀和清單檢視。新增名為「view module」(將其命名為
ic_grid_layout
) 和「view list」(將其命名為ic_linear_layout
) 的 vector assets。如需複習新增 material icons 的操作方式,請參閱此資訊頁面的操作說明。

- 您必須設法告知系統應用程式列要顯示的選項,以及使用的圖示。方法是在「res」資料夾上按一下滑鼠右鍵,然後依序選取「New」>「Android Resource File」,藉此新增資源檔案。將「Resource Type」設為
Menu
,並將「File Name」設為layout_menu
。

按一下「OK」。
開啟「res/Menu/layout_menu」。以下列內容取代
layout_menu.xml
的內容:
1 | <menu xmlns:android="http://schemas.android.com/apk/res/android" |
此選單檔案的結構很簡單。就像版面配置會在開頭透過版面配置管理工具來保留個人檢視畫面,選單 XML 檔案會在開頭使用包含個別選項的選單標記。
您的選單只有一個按鈕,並包含一些屬性:
id
:就像檢視畫面,選單選項在程式碼中也有可以參考的識別碼。title
:在本範例中其實不會顯示文字,但螢幕閱讀器可能使用文字識別選單icon
:預設為ic_linear_layout
。但選取按鈕後,系統會開啟或關閉按鈕,顯示網格圖示。showAsAction
:告訴系統如何顯示按鈕。由於它設定為 always,這個按鈕會始終在應用程式列中可見,並且不會成為溢出選單(overflow menu)的一部分。
您仍須在 MainActivity.kt
中新增一些程式碼,才能讓選單順利運作。
實作 Menu button
若要查看 menu button 實際的運作情形,您必須在 MainActivity.kt
中執行以下步驟。
- 首先,建議您建立屬性(property),追蹤應用程式所在的版面配置狀態,這樣做可讓您更輕鬆切換 layout 按鈕。將預設值設為
true
,因為預設會使用 linear layout manager。
1 | private var isLinearLayoutManager = true |
- 當使用者切換按鈕時,您會希望 item 清單(item list)轉換成 item 的格狀清單(grid list)。不曉得您是否記得,我們在 recycler views 的課程中提到很多不同的版面配置管理工具,其中的
GridLayoutManager
可以在單一資料列顯示多個 item。
1 | private fun chooseLayout() { |
- 在這個步驟中,您可以使用
if
陳述式指派 layout manager。除了設定layoutManager
外,此程式碼也會指派 adapter。LetterAdapter
會用於 list 和 grid layout。
- 一開始在 xml 中設定 menu 時,您會指定靜態圖示。但切換 layout 後,為了反映新功能,建議您更新圖示,並切換回 list layout。在這個步驟中,您只要設定 liner 和 grid layout 圖示,而在下次輕觸按鈕後,按鈕會根據 layout 切換回圖示。
1 | private fun setIcon(menuItem: MenuItem?) { |
根據 isLinearLayoutManager
屬性,系統會有條件地設定圖示。
若要應用程式順利使用 menu,您必須覆寫另外兩種方法。
onCreateOptionsMenu
:這會加載 option menu,並執行其他設定onOptionsItemSelected
:選取按鈕後,這會實際呼叫chooseLayout()
。
- 依照下列方式覆寫
onCreateOptionsMenu
:
1 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { |
以下方式並不難。加載 layout 後,請呼叫 setIcon()
,確保圖示符合 layout。這個方法會傳回 Boolean
,因為您要建立 option menu,所以會傳回 true
。
- 只要在
onOptionsItemSelected
加入幾行程式碼實作,如下所示。
1 | override fun onOptionsItemSelected(item: MenuItem): Boolean { |
每次輕觸 menu item 都會呼叫這個程式碼,所以請務必檢查輕觸的 memu item。您會使用上述的 when
陳述式。如果 id
與 action_switch_layout
memu item 相符,系統會取消 isLinearLayoutManager
的值。接著,請呼叫 chooseLayout()
和 setIcon()
,更新使用者介面。
此外,執行應用程式前,因為 chooseLayout()
中已設定 layout manager 和 adapter,您必須在 onCreate()
中更換程式碼,才能呼叫新方法。變更完成後,onCreate()
應該會如下所示。
1 | override fun onCreate(savedInstanceState: Bundle?) { |
接著請執行應用程式,您可以使用 menu button,切換 list 和格狀檢視。
解決模擬器WIFI無網路
以MAC示範:
- 打開系統設定 > 網路 > 點選 WIFI 的詳細資料

- 切換到 DNS > 新增 8.8.8.8 和 8.8.4.4 並套用

- 最後重啟模擬器,即可看到 AndroidWifi 顯示已連線(Connected)

總結
- 明確意圖(explicit intent)會用來前往應用程式特定的活動。
- 隱含意圖(implicit intent)會對應特定動作 (例如開啟連結或共用圖片),並讓系統決定如何執行意圖。
- 選單選項(Menu options)讓您可在應用程式列加入按鈕和選單。
- 透過隨附物件(Companion objects),您可以將能重複使用的常數和類型建立關聯,而非與該類型的 instance 建立關聯。
若要執行意圖(intent):
- 取得結構定義(context)的參考(reference)。
- 建立
Intent
物件提供活動(activity)或 intent type (視 intent 為 explicit 或 implicit 而定)。 - 呼叫
putExtra()
傳遞任何必要資料。 - 呼叫
startActivity()
傳入intent
物件。