[Google Course] Android Basics in Kotlin(第5篇) — ViewBinding
在這篇文章中我們將會繼續第4篇文章製作的小費計算App來學習使用ViewBinding如何在Kotlin 與UI做互動,Android Studio 3.6 Canary 11 加入了ViewBinding的功能,可以用來取代原本的findViewById()
ViewBinding 需要使用AS 3.6 Canary 11之上的版本,現在Andoird Studio 已經更新到4.1,所以大家快更新起來,可以使用更多更新的功能
我們在這篇文章會學到以下主題:
為了怕有些人沒有看之前的文章,文章中會使用到的編譯環境與參考內容也在這邊列出給大家:
Kotlin 線上編譯環境
Android Studio 安裝
Google Course 的 Android Basics in Kotlin
沒有小費計算App的人可以使用空專案來進行這篇文章的教學內容,在修改專案之前我們先來看看Android 專案裡面到底有哪些東西,在你的Android Project 視窗,點擊Android
專案架構主要分為幾個部分,如下說明:
而我們這次就是要將MainActivity的Kotlin程式,與activity_main.xml做一個連動(不是透過findViewById()
),接著就讓我們來看看ViewBinding的使用吧
[View Binding]
之前要獲得.xml內的元件來做運算,我們必須去.xml找到元件id,接著去Kotlin程式用findViewById()
才能獲得到元件,試想當我們的App越來越多功能,元件也隨之增加,整個排版會非常複雜,使用findViewById()
會變得相當累人
所以Android 提供了 view binding 的功能,在編譯時先花一點時間將.xml檔案整理成一個Kotlin類別,使我們更輕鬆地去獲取到我們.xml的元件
首先,我們要先在我們的App的Gradle檔案啟用 view binding這個功能,打開app 模組內的build.gradle檔案,在android區塊中新增下列程式碼,將viewBinding這個功能啟用:
android{
.....
buildFeatures {
viewBinding = true
}
.....
}
並且在 dependencies
區塊加上:
implementation 'com.android.databinding:viewbinding:4.1.1'
build.gradle 修改後,Android Studio 需要重新去檢視需要的函式庫及建立App相關設定所以需要重新同步,按下上方的”Sync Now”:
完成後你會看到“Gradle sync finished”的提示訊息,接著我們就可以打開我們的MainActivity來寫ViewBinding 的程式了
之後到Build->Rebuild Project,先建立一次專案之後我們來看看Android到底如何做到ViewBinding的吧!
當我們啟用ViewBinding之後建立專案時,Android 會在app->build產生我們的.xml檔案的關聯類別
舉例來說,我們有一個介面叫activity_main.xml,那在完成建立專案後,會產生一個類別叫ActivityMainBinding.java
ActivityMainBinding.java裡面已經定義好所有在activity_main.xml具有 id 屬性的元件,我們打開 ActivityMainBinding.java 一樣也能夠找到findViewById這個方法,所以其實 ViewBinding 一樣也是透過 findViewById 來連結我們的UI,而我們看到裡面的內容:
public final class ActivityMainBinding implements ViewBinding
{
@NonNull
private final ConstraintLayout rootView;
@NonNull
public final Button calculateButton;
@NonNull
public final EditText costOfService;
........
@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater)
{
return inflate(inflater, null, false);
}
.......
}
我們同時也看到,ViewBinding具有兩個特色:
1. Type safety : 元件種類永遠都會和你設計的.xml檔案內元件是相符合的,因為此檔案正是根據你的.xml所產生出來的
2. Null safety:在產生類別時,會根據是否有存在元件配置來設置NonNull註釋,因此不會使用到null 的元件
也因為這兩個原因,Google大推特推使用ViewBinding來減少開發時間以及降低開發錯誤!!
在我們的MainActivity 類別中加入ActivityMainBinding(按下alt+enter 自動匯入需要的類別 ):
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
lateinit 是一個Kotlin提供的新的關鍵字,在變數宣告時加上 lateinit ,我們無法在編譯時就先初始化binding這個變數,因此Kotlin提供這個方法,相信開發者保證能夠在之後的程式為這個變數賦值,若之後沒有賦值,則App執行時會崩潰
我們將super.onCreate()
下方程式碼修改如下:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}
- 從ActivityMainBinding 的inflate方法讓MainActivtiy 與 介面Layout 做一個綁定,並產生關聯類別的物件,指定給binding變數
- 將setContentView 的參數從 R.layout.activity_main 變為 binding.root
這之中,做了一些小小的改變,卻可以大大的簡化我們之後開發的程式:
可以看下列的範例程式的變化:
// 1. 原本我們用 findViewById()
val myButton: Button = findViewById(R.id.calculate_button)
myButton.text = "A button"
// 2. 接著我們使用 view binding
val myButton: Button = binding.calculate_button
myButton.text = "A button"
// 3. 後來我們刪除不需要的變數,直接賦值給binding 的 calculate_button
binding.calculate_button.text = "A button"
接著我們回到TipTime這個App,來實際練習ViewBinding吧!!
● 幫按鍵添加點擊監聽
因為有了binding 這個物件變數來幫我們連動.xml的元件,因此我們要添加按鍵監聽事件只需要在 setContentView(binding.root)
之後加上一行程式:
binding.calculateButton.setOnClickListener{ calculateTip() }
這個監聽事件會呼叫calculateTip()這個函式,所以我們在onCreate()這個韓式的下方,但還是在MainActivity類別內,先新增一個函式,待會為他添加程式去確認UI元件及計算小費:
fun calculateTip() {
}
● 取得使用者輸入的花費
接著我們要來完成 calculateTip()這個函式,首先我們要取得到使用者輸入到EditView的數字內容,在calculateTip()函式裡加入下面三行:
fun calculateTip() {
/// Get the cost of service
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
}
- 取得 binding 內的 costOfService 的 text,而從 costOfService 這個元件取得的 text 是可編輯的,因此我們需要透過 toString() 將它轉換為 Kotlin 中的字串,並儲存在
stringInTextField
這個變數裡 - 接著我們將
stringInTextField
字串透過 toDouble() 轉換成數字儲存在 cost 變數,如此一來接下來的指令可以使用這個 cost 數字變數來做計算
● 取得服務品質百分比
接續剛剛的程式碼,加入下方八行程式:
fun calculateTip() {
/// Get the cost of service
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
/// Get the tip percentage
val selectedId = binding.tipOptions.checkedRadioButtonId
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
}
- 一樣透過 binding 取得 tipOptions 這個 group 的被選擇的選項的 id — checkedRadioButtonId ,儲存在selectedId這個變數
- 接著我們來決定取得到的 id 應該是多少百分比,我們有很多種做法可以完成:if…else, swich, 或是 Kotlin 提供的新語法 — when
回憶此系列第二篇提到的when 語法,我們可以依據 selectedId 是哪一個來決定我們的小費比例需要多少,並且將百分比儲存在 tipPercentage 這個變數
● 確認是否進位並計算最後結果
最後一步我們要來計算最後的結果:
fun calculateTip() {
/// Get the cost of service
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
/// Get the tip percentage
val selectedId = binding.tipOptions.checkedRadioButtonId
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
} ///Calculate the tip and round it up
var tip = tipPercentage * cost
val roundUp = binding.roundUpSwitch.isChecked
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
binding.tipResult.text = tip.toString()
}
- 將 tipPercentage 乘上 cost 儲存在
var tip
這個變數 - 透過binding取得 roundUpSwitch 的 isChecked 結果,儲存在
val roundUp
這個變數裡 - 用if 判斷式判斷 roundUP,若為true,則透過 kotlin.math.ceil 將 tip 進行無條件進位
- 透過binding取得 tipResult 的元件,並且將 tipResult 的 text 設定成tip.toString()
現在你可以執行你的程式看看,輸入花費及選擇服務品質後,按下按鍵後應該可以自動算出你應該要給多少小費:
這邊可以再帶入一個小觀念,在我們設計App的時候,一般來說都會盡量提供給使用者直覺的介面,不過上方我們計算小費的結果顯示只是個數字,如果沒有特別將它匡起來,使用者可能看不出來那是個結果
我們想到第一個方法可能是直接在數字前面加上一個 ”$” 符號,而Kotlin 也貼心的提供給我們”$”符號的Library,因為各國的貨幣表示符號可能都不相同,在美國可能表示為$1,234.56,在歐洲可能表示為€1.234,56
這個方便的函式就是NumberFormat.getCurrencyInstance()
,將下列兩行取代掉原本的 binding.tipResult.text = tip.toString()
:
val formattedTip = NumberFormat.getCurrencyInstance().format(tip)
binding.tipResult.text = formattedTip
效果會像下圖右下角,函式直接在數字前方加上貨幣符號:
[在Kotlin中格式化字串及數字]
接著我們要將顯示的部分利用 stringx.xml 的格式化字串功能,來讓我們的介面更加直覺,這邊的格式化字串就有點像是C/C++的 printf
第一個參數是格式(format),像是 ”%s %d\n”,第二個之後的參數則會跟前面的格式有關
● 首先我們要先在strings.xml裡先訂出我們要的格式,打開res-> values-> strings.xml,在第一個 string 標籤的下面,一樣是resources標籤的裡面新增下面一行:
<string name="tip_amount">Tip Amount: %s</string>
● 接著我們只需要使用getString(String format, ….),就可以將一些變數按照我們定出的格式填入字串,將原本的 formattedTip
置換成下面粗體字的部分:
fun calculateTip() {
val stringInTextField = binding.costOfService.text.toString()
val cost = stringInTextField.toDouble()
val selectedId = binding.tipOptions.checkedRadioButtonId
val tipPercentage = when (selectedId) {
R.id.option_twenty_percent -> 0.20
R.id.option_eighteen_percent -> 0.18
else -> 0.15
}
var tip = tipPercentage * cost
val roundUp = binding.roundUpSwitch.isChecked
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
val formattedTip =NumberFormat.getCurrencyInstance().format(tip)
binding.tipResult.text = getString(R.string.tip_amount, formattedTip)
}
現在就算是真的完成了我們的小費計算App囉!!下面列出這篇文章額外參考的網站以及QA,大家完成了可以來測試一下是否有吸收囉!
[QA]
● ViewBinding 需要使用至少哪個版本的Android Gradle以上的版本才能夠使用?
● 如何在Android Studio 啟用 ViewBinding功能?
● 如果有一個 layout 叫 activity_calculate.xml ,那 ViewBinding 產生出來的類別名稱會叫什麼?
● 如何取得
● ViewBinding具有哪兩個特色,因此 Google 推薦使用 ?
● 嘗試透過strings.xml及 getString 在畫面顯示 "Your total cost is $200 and the extra tip is $40."
喜歡我的文章的人也記得幫我按個拍手、分享,覺得很不錯的可以幫我拍個50下!
也要快點追蹤我的 FB粉絲專頁 — 飛比尋常的程式設計世界 ,不會太頻繁出現在你的塗鴉牆騷擾你,好文章生產需要一點時間,有錯誤或想討論的都歡迎留言給我唷!那就下次見拉!