從 Android Studio 3.6 開始乡小,視圖綁定能夠通過生成綁定對象來替代findViewById隘庄,從而可以幫您簡化代碼缝彬、移除 bug缰趋,并且從 findViewById 的模版代碼中解脫出來财忽。
本文梗概
在 build.gradle 中就可以方便快捷地開啟視圖綁定且無須額外引入依賴庫
視圖綁定會為 Module 中的每一個布局文件生成一個綁定對象
(activity_awesome.xml → ActivityAwesomeBinding.java)布局文件中每一個帶有 id 的視圖都會在綁定對象中有一個對應的屬性倘核,這個屬性將擁有正確的類型,并且空安全
視圖綁定完美支持 Java 和 Kotlin 編程語言
Bilibili 視頻鏈接
https://www.bilibili.com/video/av95393509/
在 build.gradle 中開啟視圖綁定
開啟視圖綁定無須引入額外依賴即彪,從 Android Studio 3.6 開始紧唱,視圖綁定將會內(nèi)建于 Android Gradle 插件中。需要打開視圖綁定的話隶校,只需要在 build.gradle 文件中配置 viewBinding 選項:
// 需要 Android Gradle Plugin 3.6.0
android {
viewBinding {
enabled = true
}
}
在 Android Studio 4.0 中漏益,viewBinding 變成屬性被整合到了 buildFeatures 選項中,所以配置要改成:
// Android Studio 4.0
android {
buildFeatures {
viewBinding = true
}
}
配置完成后深胳,視圖綁定就會為所有布局文件自動生成對應的綁定類绰疤。無須修改原有布局的 XML 文件,視圖綁定將根據(jù)您現(xiàn)有的布局自動完成所有工作舞终。
視圖綁定將會根據(jù)現(xiàn)有的 XML 文件轻庆,為 Module 內(nèi)所有的布局文件生成綁定對象癣猾。
您可以在任何需要填充布局的地方使用綁定對象,比如 Fragment余爆、Activity纷宇、甚至是 RecyclerView Adapter(或者說是 ViewHolder 中)。
在 Activity 中使用視圖綁定
假如您有一個布局文件名叫 activity_awesome.xml龙屉,其中包含了一個按鈕和兩個文本視圖呐粘。視圖綁定會為這個布局生成一個名叫 ActivityAwesomeBinding 的類,布局文件中所有擁有 id 的視圖转捕,都會在這個類中有一個對應的屬性:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityAwesomeBinding.inflate(layoutInflater)
binding.title.text = "Hello"
binding.subtext.text = "Concise, safe code"
binding.button.setOnClickListener { /* ... */ }
setContentView(binding.root)
}
△ 在 Activity 中使用視圖綁定
使用視圖綁定時作岖,無須再調(diào)用 findViewById 方法,只要直接調(diào)用綁定對象中的對應屬性即可五芝。
布局的根視圖(無論有沒有 id)都會自動生成一個名為 root 的屬性痘儡。在 Activity 的 onCreate 方法中,要將 root 傳入 setContentView 方法枢步,從而讓 Activity 可以使用綁定對象中的布局沉删。
一個常見的錯誤用法是: 在開啟了視圖綁定的同時,依然在 setContentView(...) 中傳入布局的 id 而不是綁定對象醉途。這將造成同一布局被填充兩次矾瑰,同時監(jiān)聽器也會被添加到錯誤的布局對象中。
解決方案: 在 Activity 中使用視圖綁定時隘擎,一定要將綁定對象的 root 屬性傳入 setContentView() 方法中殴穴。
使用綁定對象編寫安全性更佳的代碼
findViewById 是許多用戶可見 bug 的來源: 我們很容易傳入一個布局中根本不存在的 id,從而導致空指針異常而崩潰货葬;由于此方法類型不安全采幌,也很容易使人寫出像 findViewById<TextView>(R.id.image) 這樣的,導致類型轉(zhuǎn)換錯誤的代碼震桶。為了解決這些問題休傍,視圖綁定把 findViewById 替換成了更加簡潔和安全的實現(xiàn)。
視圖綁定有下面兩個特性:
- 類型安全: 因為視圖綁定總是會基于布局中的視圖生成類型正確的屬性蹲姐。所以如果您在布局中放入了一個 TextView 磨取,視圖綁定就會暴露一個 TextView 類型的屬性給您。
- 空安全: 視圖綁定會檢測某個視圖是不是只在一些配置下存在淤堵,并依據(jù)結果生成帶有 @Nullable 注解的屬性寝衫。所以即使在多種配置下定義的布局文件,視圖綁定依然能夠保證空安全拐邪。
由于生成的綁定類是普通的 Java 類慰毅,并且其中添加了 Kotlin 友好的注解,所以 Java 和 Kotlin 都可以使用視圖綁定扎阶。
視圖綁定生成的代碼是怎樣的
如前文所說汹胃,視圖綁定會生成一個包含替代 findViewById 功能的 Java 類婶芭。它會為 Module 下的每一個布局的 XML 文件生成一個對應的綁定對象,并根據(jù)源文件為其命名着饥,比如 activity_awesome.xml 對應的綁定對象為 ActivityAwesomeBinding.java犀农。
生成代碼的邏輯被優(yōu)化為,當您在 Android Studio 中編輯 XML 布局文件時宰掉,只會更新所修改布局對應的綁定對象呵哨。同時這些工作會在內(nèi)存中運行,從而使這個過程可以迅速完成轨奄。這意味著您的修改會立即反映在綁定對象中孟害,而無須等待或者重新構建工程。
Android Studio 被優(yōu)化為可以在您編輯過 XML 布局文件后立即更新綁定對象挪拟。
讓我們通過一個示例 XML 布局所生成的代碼挨务,來了解一下視圖綁定究竟生成了什么。
public final class ActivityAwesomeBinding implements ViewBinding {
@NonNull
private final ConstraintLayout rootView;
@NonNull
public final Button button;
@NonNull
public final TextView subtext;
@NonNull
public final TextView title;
△ 視圖綁定生成的屬性玉组』驯可以看到它們都是類型安全以及空安全的
視圖綁定會根據(jù)每個擁有 id 的視圖生成類型正確的屬性。他也會為根布局生成 rootView 屬性并通過 getRoot 暴露給您惯雳。視圖綁定沒有添加任何額外的邏輯朝巫,他只是把視圖屬性暴露給您,從而幫您在不使用 findViewById 的情況下也能調(diào)用它們石景。這樣一來便保證了生成文件簡潔性(當然也避免了拖慢構建速度)捍歪。
如果您正在使用 Kotlin,視圖綁定的生成類也已經(jīng)對互操作進行了優(yōu)化鸵钝。通過 @Nullable 和 @NonNull 注解的使用,Kolin 可以正確的將屬性暴露為空安全類型庐镐。如果想要了解更多關于兩種語言的互操作問題恩商,請查閱文檔: 在 Kotlin 中調(diào)用 Java。
private ActivityAwesomeBinding(@NonNull ConstraintLayout rootView, @NonNull Button button,
@NonNull TextView subtext, @NonNull TextView title) { … }
@NonNull
public static ActivityAwesomeBinding inflate(@NonNull LayoutInflater inflater) {
/* 編輯過: 移除了重載方法 inflate(inflater, parent, attachToParent) 的調(diào)用*/
View root = inflater.inflate(R.layout.activity_awesome, null, false);
return bind(root);
}
視圖綁定會生成 inflate 方法作為生成一個綁定對象實例的主要方式必逆。在 ActivityAwesomeBinding.java 中怠堪,視圖綁定生成了一個只有一個參數(shù)的 inflate 方法,該方法通過將 parent 設定為空值來指定當前視圖不會綁定到父視圖中名眉;視圖綁定也暴露了一個有三個參數(shù)的 inflate 方法粟矿,來讓您在需要的時候傳入 parent 和 attachToParent 參數(shù)。
真正神奇的地方是 bind 方法的調(diào)用损拢。這里會填充視圖并綁定所有的屬性陌粹,同時做一些錯誤檢測并生成清晰的錯誤提示。
@NonNull
public static ActivityAwesomeBinding bind(@NonNull View rootView) {
/* 編輯: 簡化代碼 – 真實情況下生成的代碼是一個優(yōu)化過的版本 */
Button button = rootView.findViewById(R.id.button);
TextView subtext = rootView.findViewById(R.id.subtext);
TextView title = rootView.findViewById(R.id.title);
if (button != null && subtext != null && title != null) {
return new ActivityAwesomeBinding((ConstraintLayout) rootView, button, subtext, title);
}
throw new NullPointerException("Missing required view […]");
}
△ 自動生成的 bind 方法的簡化版本
bind 是綁定對象中最復雜的一個方法福压,它通過調(diào)用 findViewById 來綁定每個視圖掏秩。既然編譯器可以通過 XML 布局文件知道每個屬性的類型和為空的可能性或舞,那他就可以安全的調(diào)用 findViewById。
請注意蒙幻,視圖綁定生成的真正的 bind 方法要來的更長映凳,并且其中使用了一個標記 break 語句來優(yōu)化字節(jié)碼,您可以查看 Jake Wharton 撰寫的這篇文章來了解更多優(yōu)化有關的內(nèi)容邮破。在每個綁定對象中诈豌,都會暴露三個靜態(tài)方法來創(chuàng)建綁定對象實例,下面是每個方法使用場景的簡要說明:
inflate(inflater) -- 在例如 Activity onCreate 方法里抒和,這類沒有父視圖需要被傳入的場合使用
inflate(inflater, parent, attachToParent) -- 在 Fragment 或 RecyclerView Adapter (或者說 ViewHolder 中) 矫渔,這類您需要傳遞父級 ViewGroup 給綁定對象時使用。
bind(rootView) -- 在您已經(jīng)獲得對應視圖构诚,并且只想通過視圖綁定來避免使用 findViewById 時使用蚌斩。這個方法在使用視圖綁定改造和重構現(xiàn)有代碼時非常有用。
示例 XML 布局
https://gist.github.com/objcode/3ee41edae40ba13f13da569b8f27333a在 Kotlin 中調(diào)用 Java
https://kotlinlang.org/docs/reference/java-interop.html#null-safety-and-platform-typesJake Wharton 撰寫的這篇文章
https://jakewharton.com/optimizing-bytecode-by-manipulating-source-code/
對使用 <include> 標簽引入的布局會發(fā)生什么影響
前面已經(jīng)講過范嘱,視圖綁定會為 Module 下的每一個布局文件生成一個綁定對象送膳,這個說法在布局文件被另一個布局文件使用 <include> 引入時依然適用。
<!-- activity_awesome.xml -->
<androidx.constraintlayout.widget.ConstraintLayout>
<include android:id="@+id/includes" layout="@layout/included_buttons"
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- included_buttons.xml -->
<androidx.constraintlayout.widget.ConstraintLayout>
<Button android:id="@+id/include_me" />
</androidx.constraintlayout.widget.ConstraintLayout>
△ 視圖綁定中使用 include 標簽的示例 注意: include 標簽下有一個 id丑蛤。
在使用引入布局的時候叠聋,視圖綁定會創(chuàng)建一個被引入布局綁定對象的引用。注意 <include> 標簽有一個 id: android:id="@+id/includes"受裹。這里的邏輯跟使用普通視圖一樣碌补, <include> 標簽也需要有一個 id 才能在綁定對象中生成對應的屬性。
include 標簽必須有一個 id棉饶,才能生成對應的屬性厦章。
public final class ActivityAwesomeBinding implements ViewBinding {
...
@NonNull
public final IncludedButtonsBinding includes;
視圖綁定會在 ActivityAwesomeBinding 中生成一個 IncludedButtonsBinding 的引用。
結合數(shù)據(jù)綁定來使用視圖綁定
視圖綁定只是 findViewById 的取代方案照藻,如果您希望在 XML 中自動綁定視圖袜啃,可以使用數(shù)據(jù)綁定庫。數(shù)據(jù)綁定和視圖綁定可以生成同樣的組件幸缕,它們可以同時工作群发。
在兩者都被開啟時,使用 <layout> 標簽的布局會由數(shù)據(jù)綁定來生成綁定對象发乔;而其余的布局則由視圖綁定生成綁定對象熟妓。
您可以在同一 Module 中同時使用數(shù)據(jù)綁定和視圖綁定。
我們之所以開發(fā)視圖綁定作為數(shù)據(jù)綁定的補充栏尚,是因為許多開發(fā)者反映說起愈,希望有一個輕量的解決方案,能在數(shù)據(jù)綁定之外替代 findViewById——視圖綁定提供的正是這一功能。
視圖綁定對比 Kotlin 合成方法與 ButterKnife
關于視圖綁定告材,一個最常見的問題是: "我是否應該用視圖綁定替代 Kotlin 合成方法或 ButterKnife ? " 二者都是目前十分成功的組件庫坤次,有許多應用使用它們解決 findViewById 的問題。
對于大多數(shù)應用來說斥赋,我們推薦嘗試使用視圖綁定來替代這兩個庫缰猴,因為視圖綁定可以提供更加安全和準確的視圖映射方式。
上圖為對比視圖綁定隘膘、ButterKnife 和 Kotlin 合成方法的功能疑故。
雖然 ButterKnife 會在運行時校驗可空與不可空,但是編譯器并不會檢查您匹配的視圖是否在存在于您的布局之中弯菊。
為了安全性與更簡潔代碼纵势,我們推薦嘗試使用視圖綁定。
點擊這里了解更多有關視圖綁定的信息