[Google Course] Android Basics in Kotlin(第5篇) — ViewBinding

Phoebe Huang
16 min readNov 29, 2020

--

在這篇文章中我們將會繼續第4篇文章製作的小費計算App來學習使用ViewBinding如何在Kotlin 與UI做互動,Android Studio 3.6 Canary 11 加入了ViewBinding的功能,可以用來取代原本的findViewById()

ViewBinding 需要使用AS 3.6 Canary 11之上的版本,現在Andoird Studio 已經更新到4.1,所以大家快更新起來,可以使用更多更新的功能

我們在這篇文章會學到以下主題:

View Binding
在Kotlin中格式化字串及數字
QA

為了怕有些人沒有看之前的文章,文章中會使用到的編譯環境與參考內容也在這邊列出給大家:
Kotlin 線上編譯環境
Android Studio 安裝
Google Course 的 Android Basics in Kotlin

沒有小費計算App的人可以使用空專案來進行這篇文章的教學內容,在修改專案之前我們先來看看Android 專案裡面到底有哪些東西,在你的Android Project 視窗,點擊Android

專案架構主要分為幾個部分,如下說明:

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這個功能啟用:

build.gradle 路徑
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)

}
}
  1. ActivityMainBinding 的inflate方法讓MainActivtiy 與 介面Layout 做一個綁定,並產生關聯類別的物件,指定給binding變數
  2. 將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()

}
  1. 取得 binding 內的 costOfService 的 text,而從 costOfService 這個元件取得的 text 是可編輯的,因此我們需要透過 toString() 將它轉換為 Kotlin 中的字串,並儲存在stringInTextField 這個變數裡
  2. 接著我們將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
}

}
  1. 一樣透過 binding 取得 tipOptions 這個 group 的被選擇的選項的 id — checkedRadioButtonId ,儲存在selectedId這個變數
  2. 接著我們來決定取得到的 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()

}
  1. tipPercentage 乘上 cost 儲存在 var tip 這個變數
  2. 透過binding取得 roundUpSwitch isChecked 結果,儲存在val roundUp 這個變數裡
  3. 用if 判斷式判斷 roundUP,若為true,則透過 kotlin.math.ceil tip 進行無條件進位
  4. 透過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粉絲專頁 — 飛比尋常的程式設計世界 ,不會太頻繁出現在你的塗鴉牆騷擾你,好文章生產需要一點時間,有錯誤或想討論的都歡迎留言給我唷!那就下次見拉!

--

--

Phoebe Huang
Phoebe Huang

Written by Phoebe Huang

A software engineer from Taiwan, use free time to learn more about computer science.

Responses (1)