1. 程式人生 > 程式設計 >Android Studio 3.6中使用檢視繫結替代 findViewById的方法

Android Studio 3.6中使用檢視繫結替代 findViewById的方法

Android Studio 3.6中使用檢視繫結替代 findViewById的方法

從 Android Studio 3.6 開始,檢視繫結能夠通過生成繫結物件來替代 findViewById,從而可以幫您簡化程式碼、移除 bug,並且從 findViewById 的模版程式碼中解脫出來。

本文梗概

  • 在 build.gradle 中就可以方便快捷地開啟檢視繫結且無須額外引入依賴庫
  • 檢視繫結會為 Module 中的每一個佈局檔案生成一個繫結物件
  • (activity_awesome.xml → ActivityAwesomeBinding.java)
  • 佈局檔案中每一個帶有 id 的檢視都會在繫結物件中有一個對應的屬性,這個屬性將擁有正確的型別,並且空安全
  • 檢視繫結完美支援 Java 和 Kotlin 程式語言

騰訊視訊連結

https://v.qq.com/x/page/h0931mdo8ly.html

Bilibili 視訊連結

https://www.bilibili.com/video/av95393509/

在 build.gradle 中開啟檢視繫結

開啟檢視繫結無須引入額外依賴,從 Android Studio 3.6 開始,檢視繫結將會內建於 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 檔案,檢視繫結將根據您現有的佈局自動完成所有工作。

檢視繫結將會根據現有的 XML 檔案,為 Module 內所有的佈局檔案生成繫結物件。

您可以在任何需要填充佈局的地方使用繫結物件,比如 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 中使用檢視繫結

使用檢視繫結時,無須再呼叫 findViewById 方法,只要直接呼叫繫結物件中的對應屬性即可。

佈局的根檢視(無論有沒有 id)都會自動生成一個名為 root 的屬性。在 Activity 的 onCreate 方法中,要將 root 傳入 setContentView 方法,從而讓 Activity 可以使用繫結物件中的佈局。

一個常見的錯誤用法是: 在開啟了檢視繫結的同時,依然在 setContentView(...) 中傳入佈局的 id 而不是繫結物件。這將造成同一佈局被填充兩次,同時監聽器也會被新增到錯誤的佈局物件中。

解決方案: 在 Activity 中使用檢視繫結時,一定要將繫結物件的 root 屬性傳入 setContentView() 方法中。

使用繫結物件編寫安全性更佳的程式碼

findViewById 是許多使用者可見 bug 的來源: 我們很容易傳入一個佈局中根本不存在的 id,從而導致空指標異常而崩潰;由於此方法型別不安全,也很容易使人寫出像 findViewById<TextView>(R.id.image) 這樣的,導致型別轉換錯誤的程式碼。為了解決這些問題,檢視繫結把 findViewById 替換成了更加簡潔和安全的實現。

檢視繫結有下面兩個特性:

  • 型別安全: 因為檢視繫結總是會基於佈局中的檢視生成型別正確的屬性。所以如果您在佈局中放入了一個 TextView ,檢視繫結就會暴露一個 TextView 型別的屬性給您。
  • 空安全: 檢視繫結會檢測某個檢視是不是隻在一些配置下存在,並依據結果生成帶有 @Nullable 註解的屬性。所以即使在多種配置下定義的佈局檔案,檢視繫結依然能夠保證空安全。

由於生成的繫結類是普通的 Java 類,並且其中添加了 Kotlin 友好的註解,所以 Java 和 Kotlin 都可以使用檢視繫結。

檢視繫結生成的程式碼是怎樣的

如前文所說,檢視繫結會生成一個包含替代 findViewById 功能的 Java 類。它會為 Module 下的每一個佈局的 XML 檔案生成一個對應的繫結物件,並根據原始檔為其命名,比如 activity_awesome.xml 對應的繫結物件為 ActivityAwesomeBinding.java。

生成程式碼的邏輯被優化為,當您在 Android Studio 中編輯 XML 佈局檔案時,只會更新所修改佈局對應的繫結物件。同時這些工作會在記憶體中執行,從而使這個過程可以迅速完成。這意味著您的修改會立即反映在繫結物件中,而無須等待或者重新構建工程。

Android Studio 被優化為可以在您編輯過 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;

△ 檢視繫結生成的屬性。可以看到它們都是型別安全以及空安全的

檢視繫結會根據每個擁有 id 的檢視生成型別正確的屬性。他也會為根佈局生成 rootView 屬性並通過 getRoot 暴露給您。檢視繫結沒有新增任何額外的邏輯,他只是把檢視屬性暴露給您,從而幫您在不使用 findViewById 的情況下也能呼叫它們。這樣一來便保證了生成檔案簡潔性(當然也避免了拖慢構建速度)。

如果您正在使用 Kotlin,檢視繫結的生成類也已經對互操作進行了優化。通過 @Nullable 和 @NonNull 註解的使用,Kolin 可以正確的將屬性暴露為空安全型別。如果想要了解更多關於兩種語言的互操作問題,請查閱文件: 在 Kotlin 中呼叫 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) 的呼叫*/
 View root = inflater.inflate(R.layout.activity_awesome,null,false);
 return bind(root);
 }

檢視繫結會生成 inflate 方法作為生成一個繫結物件例項的主要方式。在 ActivityAwesomeBinding.java 中,檢視繫結生成了一個只有一個引數的 inflate 方法,該方法通過將 parent 設定為空值來指定當前檢視不會繫結到父檢視中;檢視繫結也暴露了一個有三個引數的 inflate 方法,來讓您在需要的時候傳入 parent 和 attachToParent 引數。

真正神奇的地方是 bind 方法的呼叫。這裡會填充檢視並繫結所有的屬性,同時做一些錯誤檢測並生成清晰的錯誤提示。

 @NonNull
 public static ActivityAwesomeBinding bind(@NonNull View rootView) {
 /* 編輯: 簡化程式碼 – 真實情況下生成的程式碼是一個優化過的版本 */
 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 是繫結物件中最複雜的一個方法,它通過呼叫 findViewById 來繫結每個檢視。既然編譯器可以通過 XML 佈局檔案知道每個屬性的型別和為空的可能性,那他就可以安全的呼叫 findViewById。

請注意,檢視繫結生成的真正的 bind 方法要來的更長,並且其中使用了一個標記 break 語句來優化位元組碼,您可以檢視 Jake Wharton 撰寫的這篇文章來了解更多優化有關的內容。在每個繫結物件中,都會暴露三個靜態方法來建立繫結物件例項,下面是每個方法使用場景的簡要說明:

  • inflate(inflater) -- 在例如 Activity onCreate 方法裡,這類沒有父檢視需要被傳入的場合使用
  • inflate(inflater,attachToParent) -- 在 Fragment 或 RecyclerView Adapter (或者說 ViewHolder 中) ,這類您需要傳遞父級 ViewGroup 給繫結物件時使用。
  • bind(rootView) -- 在您已經獲得對應檢視,並且只想通過檢視繫結來避免使用 findViewById 時使用。這個方法在使用檢視繫結改造和重構現有程式碼時非常有用。

示例 XML 佈局
https://gist.github.com/objcode/3ee41edae40ba13f13da569b8f27333a
在 Kotlin 中呼叫 Java
https://kotlinlang.org/docs/reference/java-interop.html#null-safety-and-platform-types
Jake Wharton 撰寫的這篇文章
https://jakewharton.com/optimizing-bytecode-by-manipulating-source-code/

對使用 <include> 標籤引入的佈局會發生什麼影響

前面已經講過,檢視繫結會為 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。

在使用引入佈局的時候,檢視繫結會建立一個被引入佈局繫結物件的引用。注意 <include> 標籤有一個 id: android:id="@+id/includes"。這裡的邏輯跟使用普通檢視一樣, <include> 標籤也需要有一個 id 才能在繫結物件中生成對應的屬性。

include 標籤必須有一個 id,才能生成對應的屬性。

public final class ActivityAwesomeBinding implements ViewBinding {
 ...

 @NonNull
 public final IncludedButtonsBinding includes;

檢視繫結會在 ActivityAwesomeBinding 中生成一個 IncludedButtonsBinding 的引用。

結合資料繫結來使用檢視繫結

檢視繫結只是 findViewById 的取代方案,如果您希望在 XML 中自動繫結檢視,可以使用資料繫結庫。資料繫結和檢視繫結可以生成同樣的元件,它們可以同時工作。

在兩者都被開啟時,使用 <layout> 標籤的佈局會由資料繫結來生成繫結物件;而其餘的佈局則由檢視繫結生成繫結物件。

您可以在同一 Module 中同時使用資料繫結和檢視繫結。

我們之所以開發檢視繫結作為資料繫結的補充,是因為許多開發者反映說,希望有一個輕量的解決方案,能在資料繫結之外替代 findViewById——檢視繫結提供的正是這一功能。

資料繫結
https://developer.android.google.cn/topic/libraries/data-binding

檢視繫結對比 Kotlin 合成方法與 ButterKnife

關於檢視繫結,一個最常見的問題是: "我是否應該用檢視繫結替代 Kotlin 合成方法或 ButterKnife ? " 二者都是目前十分成功的元件庫,有許多應用使用它們解決 findViewById 的問題。

對於大多數應用來說,我們推薦嘗試使用檢視繫結來替代這兩個庫,因為檢視繫結可以提供更加安全和準確的檢視對映方式。

Android Studio 3.6中使用檢視繫結替代 findViewById的方法

△ 檢視繫結空安全、只引用當前佈局中的檢視、支援 Java 和 Kotlin,同時也更簡潔

上圖為對比檢視繫結、ButterKnife 和 Kotlin 合成方法的功能。

雖然 ButterKnife 會在執行時校驗可空與不可空,但是編譯器並不會檢查您匹配的檢視是否在存在於您的佈局之中。

為了安全性與更簡潔程式碼,我們推薦嘗試使用檢視繫結。

總結

到此這篇關於Android Studio 3.6中使用檢視繫結替代 findViewById的方法的文章就介紹到這了,更多相關使用檢視繫結替代 findViewById內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!