接下來的兩個章節會收錄一些獨立的小主題,主題之間不會有關聯,讀者可以另開一個專案跟著實作。
在程式設計過程可以透過訪問組態取得一些資訊,透過 resources.configuration.xxx
,可以得知許多設定參數值。
以下挑選五種,讀者可以將程式碼寫在 onCreate()
之中,觀察 Logcat 印出來的資訊:
Log.i("SHOW_CONFIG", "screenWidthDp: ${resources.configuration.screenWidthDp}")
Log.i("SHOW_CONFIG", "screenHeightDp: ${resources.configuration.screenHeightDp}")
Log.i("SHOW_CONFIG", "densityDpi: ${resources.configuration.densityDpi}")
Log.i("SHOW_CONFIG", "fontScale: ${resources.configuration.fontScale}")
Log.i("SHOW_CONFIG", "orientation: ${resources.configuration.orientation}")
I/SHOW_CONFIG: screenWidthDp: 411
I/SHOW_CONFIG: screenHeightDp: 750
I/SHOW_CONFIG: densityDpi: 560
I/SHOW_CONFIG: fontScale: 1.15
I/SHOW_CONFIG: orientation: 1
使用模擬器旋轉一下設備的方向,可以觀察到 Width、Height、Orientation 的值皆產生變化,fontSacle 是手機系統設定字體大小,預設會是 1,作者有在設定 > 顯示 > 字體大小調整過,所以範例中會顯示 1.15 倍。
要使用 orientation
來做判斷時,可以利用 Configuration.ORIENTATION_LANDSCAPE
取代使用 if (resources.configuration.orientation == 1)
這種方式,參照下列示範搭配 when
判斷:
Log.i("SHOW_CONFIG", "orientation: ${resources.configuration.orientation}")
Log.i("SHOW_CONFIG", "ORIENTATION_PORTRAIT: ${Configuration.ORIENTATION_PORTRAIT}")
Log.i("SHOW_CONFIG", "ORIENTATION_LANDSCAPE: ${Configuration.ORIENTATION_LANDSCAPE}")
when (resources.configuration.orientation) {
Configuration.ORIENTATION_PORTRAIT -> {
Log.i("SHOW_CONFIG", "直")
// TODO
}
Configuration.ORIENTATION_LANDSCAPE -> {
Log.i("SHOW_CONFIG", "橫")
// TODO
}
}
在程式設計中,也可以透過 resources.configuration.screenWidthDp > 一個整數
,判斷目前設備的顯示寬度,並提供適應大小的排版變化,例如昨天課程使用的 GridLayoutManager(this, 2)
,可以設計成當螢幕大於某個寬度時,改以多於 2
欄的方式排列。
接著的小節要介紹的是對話方塊,在 Android 中可以透過 AlertDialog API 來實作,首先在版面上增加一個按鈕當作觸發,按鈕名稱訂 alertBtn
。對話方塊的實作很簡單,使用 AlertDialog.Builder
建立 AlertDialog 主體,再透過 show()
即可顯示方塊。
class MainActivity : AppCompatActivity() {
lateinit var alertDialog: AlertDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 設計對話方塊
val alertBuilder = AlertDialog.Builder(this)
alertBuilder
.setTitle("標題")
.setMessage("內容")
// 建立對話方塊
alertDialog = alertBuilder.create()
// 按鈕觸發顯示對話方塊
alertBtn.setOnClickListener { alertDialog.show() }
}
}
上方的程式碼為了方便介紹所以放在一起,設計與建立這兩個動作可以另外擷取成一個方法, 獨立出來讓程式不會混在一起。
下一步驟是在方塊上增加按鈕,在 alertBuilder.create()
上方插入新增按鈕的程式碼,Dialog 有三大區塊:標題、內容、動作按鈕,其中動作按鈕最多可以建立三種按鈕,分別是:
1. 正面:通常代表接受內容繼續執行
2. 負面:代表取消動作
3. 中立:使用者暫時不想決定時,例如稍後提醒的選項
按鈕建立方式如下,可以在 set...Button()
時指定按鈕的文字,並利用 { }
實作按下按鈕後要執行的動作,其中可以使用兩個參數 dialog, witch
。在下面的範例共用一個函式,透過 which
變數可以得知哪一個按鈕觸發,which
的值作者以註解方式加在各段設定按鈕的後方。
alertBuilder
.setPositiveButton("確定") { dialog, which ->
showWitch(dialog, which) // -1
}
.setNegativeButton("拒絕") { dialog, which ->
showWitch(dialog, which) // -2
}
.setNeutralButton("稍後決定") { dialog, which ->
showWitch(dialog, which) // -3
}
// 在 onCreate 之外建立的函式
private fun showWitch(dialog: DialogInterface?, which: Int) {
Log.i("SHOW_CONFIG", which.toString())
}
特別提醒:由於教學示範方便,所以並無使用文字資源管理的方式,正式開發應將 set...Button()
指定按鈕動作的名稱整合至 strings.xml。
上面示範的是對話方塊,接著接著的是選擇清單,一樣有分三種型態:
val options = arrayOf("選項一", "選項二", "選項三")
val alertBuilder = AlertDialog.Builder(this)
alertBuilder
.setTitle("標題")
.setItems(options) { _, which ->
Log.i("SHOW_CONFIG", which.toString())
}
setSingleChoiceItems()
建立確認式的選項清單,並且需要再設定兩個按鈕作為正負面選擇。 setSingleChoiceItems()
需要兩個參數,第一個是選項組,第二個是預設選項的索引值,選項組的部分讀者可以沿用剛剛宣告的 options
,作者這邊另外再介紹一種方式:
alertBuilder
.setTitle("標題")
.setSingleChoiceItems(R.array.options, -1) { _, which ->
Log.i("SHOW_CONFIG", which.toString())
}
於 res> values
新增一個 xml 資源檔案,名為 array.xml,依照下列格式建立資源,可以是 string-array
或 integer-array
,name
是決定陣列的名稱,在程式碼 R.array.[名稱]
會使用到。另外提醒同樣觀念,選項文字也應該整合至文字資源檔中。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="options">
<item>選項一</item>
<item>選項二</item>
<item>選項三</item>
</string-array>
</resources>
接著再加上兩個按鈕的設定就可以完成,注意在 setSingleChoiceItems()
這裡,因為用不到 dialog
可以用底線 _
取代掉,which
會等於選到的項目索引。兩個 Button
裡面的 which
仍然是按鈕的識別碼,跟 setSingleChoiceItems()
之中的 which
是選項的識別碼不相同。
alertBuilder
.setTitle("標題")
.setSingleChoiceItems(R.array.options, -1) { _, which ->
Log.i("SHOW_CONFIG", which.toString())
}
.setPositiveButton("確定") { dialog, which ->
showWitch(dialog, which)
}
.setNegativeButton("取消") { dialog, which ->
showWitch(dialog, which)
}
將剛才的 setSingle...
取代成 setMulti...
,第二個參數需要更換,現在的接收型態已變為布林陣列,如果沒有要預設選項,則可以用 null
表示全不選。
.setMultiChoiceItems(R.array.options, null) { _, which, isChecked ->
Log.i("SHOW_CONFIG", "$which $isChecked")
}
後方的 lambda
表示式也需要再加一個參數 isChecked
,用來獲取每次點擊時是將選項勾選還是取消,Log.i
的部分有加入一點變化,現在可以執行程式,觀察一下每次點選項目時會如何觸發。
透過 Logcat 的資訊,可以知道每次勾選選項時,會得到該選項及變更值,因此可以使用一個容器統一裝載,以下程式碼示範如何記錄勾選的項目:
// 取得 XML 中陣列長度
val arraySize = resources.getStringArray(R.array.options).size
// 宣告一個相同大小的布林陣列,預設為 false,也可指定為 true 讓開啟對話方塊預設全選
var arrOpts = BooleanArray(arraySize) { false }
val alertBuilder = AlertDialog.Builder(this)
alertBuilder
.setTitle("標題")
// 第二參數 null 改為 arrOpts
.setMultiChoiceItems(R.array.options, arrOpts) { _, which, isChecked ->
// 每次勾選項目時,將陣列對應值更新
arrOpts[which] = isChecked
Log.i("SHOW_CONFIG", "${arrOpts.toList()}")
}
.
.
.
今天只介紹了 Dialog 基礎部分,所展示的都是系統預設樣式,對話方塊也可以跟之前示範的 FoodGroup、FoodItem 清單項目一樣,透過 xml 自訂對話方塊的顯示樣式,可以參考這裡的步驟完成自訂樣式,不過官方文件上還是以 Java 語言格式呈現,讀者可以參照這篇教學了解 Java 與 Kotlin 之間的轉換要如何替代,雖說官方標榜 Android Studio IDE 會自動轉換,但在撰寫本篇文章時,太過複雜的部分自動轉換後仍是需要自行調整的。
因此本篇重點在於 Kotlin 的撰寫方式,比起 Java 寫法,在查詢語言設計方式的相關資源相對較少,作者也花了點時間從官方版本轉成可執行的 Kotlin 版本,至於 Dialog 共通與進階使用的概念部分已經有很多豐富的線上資源,就不再重複介紹了,我們明天見!
資料參考
Log-Android Developers
https://developer.android.com/guide/topics/ui/dialogs