AndroidStudio 3.6 視圖綁定替代 findViewById

AndroidStudio 3.6 視圖綁定替代 findViewById

簡要

  • 在 build.gradle 中就可以方便快捷地開啓視圖綁定且無須額外引入依賴庫

  • 視圖綁定會爲 Module 中的每一個佈局文件生成一個綁定對象

    (activity_awesome.xml → ActivityAwesomeBinding.java)

  • 佈局文件中每一個帶有 id 的視圖都會在綁定對象中有一個對應的屬性,這個屬性將擁有正確的類型,並且空安全

  • 視圖綁定完美支持 Java 和 Kotlin 編程語言

在 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(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 ConstraintLayou
@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, parent, 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/

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

前面已經講過,視圖綁定會爲 Module 下的每一個佈局文件生成一個綁定對象,這個說法在佈局文件被另一個佈局文件使用 引入時依然適用。

<!-- 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。

在使用引入佈局的時候,視圖綁定會創建一個被引入佈局綁定對象的引用。注意 標籤有一個 id: android:id="@+id/includes"。這裏的邏輯跟使用普通視圖一樣, 標籤也需要有一個 id 才能在綁定對象中生成對應的屬性。

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

 public final class ActivityAwesomeBinding implements ViewBinding {  ...
  @NonNull  public final IncludedButtonsBinding includes;

視圖綁定會在 ActivityAwesomeBinding 中生成一個 IncludedButtonsBinding 的引用。

結合數據綁定來使用視圖綁定

視圖綁定只是 findViewById 的取代方案,如果您希望在 XML 中自動綁定視圖,可以使用數據綁定庫。數據綁定和視圖綁定可以生成同樣的組件,它們可以同時工作。

在兩者都被開啓時,使用 標籤的佈局會由數據綁定來生成綁定對象;而其餘的佈局則由視圖綁定生成綁定對象。

您可以在同一 Module 中同時使用數據綁定和視圖綁定。

我們之所以開發視圖綁定作爲數據綁定的補充,是因爲許多開發者反映說,希望有一個輕量的解決方案,能在數據綁定之外替代 findViewById——視圖綁定提供的正是這一功能。

  • 數據綁定

    https://developer.android.google.cn/topic/libraries/data-binding

視圖綁定對比 Kotlin 合成方法與 ButterKnife

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

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

img

△ 視圖綁定空安全、只引用當前佈局中的視圖、支持 Java 和 Kotlin,同時也更簡潔

上圖爲對比視圖綁定、ButterKnife 和 Kotlin 合成方法的功能。

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

爲了安全性與更簡潔代碼,我們推薦嘗試使用視圖綁定。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章