使用視圖綁定替代 findViewById

從 Android Studio 3.6 開始,視圖綁定能夠通過生成綁定對象來替代findViewById,從而可以幫您簡化代碼、移除 bug,并且從 findViewById 的模版代碼中解脫出來。

本文梗概

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

  • 視圖綁定會(huì)為 Module 中的每一個(gè)布局文件生成一個(gè)綁定對象
    (activity_awesome.xml → ActivityAwesomeBinding.java)

  • 布局文件中每一個(gè)帶有 id 的視圖都會(huì)在綁定對象中有一個(gè)對應(yīng)的屬性,這個(gè)屬性將擁有正確的類型,并且空安全

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

  • 騰訊視頻鏈接
    https://v.qq.com/x/page/h0931mdo8ly.html

  • Bilibili 視頻鏈接
    https://www.bilibili.com/video/av95393509/

在 build.gradle 中開啟視圖綁定

開啟視圖綁定無須引入額外依賴,從 Android Studio 3.6 開始,視圖綁定將會(huì)內(nèi)建于 Android Gradle 插件中。需要打開視圖綁定的話,只需要在 build.gradle 文件中配置 viewBinding 選項(xiàng):

// 需要 Android Gradle Plugin 3.6.0
android {
    viewBinding {
        enabled = true
    }
}

在 Android Studio 4.0 中,viewBinding 變成屬性被整合到了 buildFeatures 選項(xiàng)中,所以配置要改成:


// Android Studio 4.0
android {
    buildFeatures {
        viewBinding = true
    }
}

配置完成后,視圖綁定就會(huì)為所有布局文件自動(dòng)生成對應(yīng)的綁定類。無須修改原有布局的 XML 文件,視圖綁定將根據(jù)您現(xiàn)有的布局自動(dòng)完成所有工作。

視圖綁定將會(huì)根據(jù)現(xiàn)有的 XML 文件,為 Module 內(nèi)所有的布局文件生成綁定對象。

您可以在任何需要填充布局的地方使用綁定對象,比如 Fragment、Activity、甚至是 RecyclerView Adapter(或者說是 ViewHolder 中)。

在 Activity 中使用視圖綁定

假如您有一個(gè)布局文件名叫 activity_awesome.xml,其中包含了一個(gè)按鈕和兩個(gè)文本視圖。視圖綁定會(huì)為這個(gè)布局生成一個(gè)名叫 ActivityAwesomeBinding 的類,布局文件中所有擁有 id 的視圖,都會(huì)在這個(gè)類中有一個(gè)對應(yīng)的屬性:


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 中使用視圖綁定

使用視圖綁定時(shí),無須再調(diào)用 findViewById 方法,只要直接調(diào)用綁定對象中的對應(yīng)屬性即可。

布局的根視圖(無論有沒有 id)都會(huì)自動(dòng)生成一個(gè)名為 root 的屬性。在 Activity 的 onCreate 方法中,要將 root 傳入 setContentView 方法,從而讓 Activity 可以使用綁定對象中的布局。

一個(gè)常見的錯(cuò)誤用法是: 在開啟了視圖綁定的同時(shí),依然在 setContentView(...) 中傳入布局的 id 而不是綁定對象。這將造成同一布局被填充兩次,同時(shí)監(jiān)聽器也會(huì)被添加到錯(cuò)誤的布局對象中。

解決方案: 在 Activity 中使用視圖綁定時(shí),一定要將綁定對象的 root 屬性傳入 setContentView() 方法中。

使用綁定對象編寫安全性更佳的代碼

findViewById 是許多用戶可見 bug 的來源: 我們很容易傳入一個(gè)布局中根本不存在的 id,從而導(dǎo)致空指針異常而崩潰;由于此方法類型不安全,也很容易使人寫出像 findViewById<TextView>(R.id.image) 這樣的,導(dǎo)致類型轉(zhuǎn)換錯(cuò)誤的代碼。為了解決這些問題,視圖綁定把 findViewById 替換成了更加簡潔和安全的實(shí)現(xiàn)。

視圖綁定有下面兩個(gè)特性:

  • 類型安全: 因?yàn)橐晥D綁定總是會(huì)基于布局中的視圖生成類型正確的屬性。所以如果您在布局中放入了一個(gè) TextView ,視圖綁定就會(huì)暴露一個(gè) TextView 類型的屬性給您。
  • 空安全: 視圖綁定會(huì)檢測某個(gè)視圖是不是只在一些配置下存在,并依據(jù)結(jié)果生成帶有 @Nullable 注解的屬性。所以即使在多種配置下定義的布局文件,視圖綁定依然能夠保證空安全。

由于生成的綁定類是普通的 Java 類,并且其中添加了 Kotlin 友好的注解,所以 Java 和 Kotlin 都可以使用視圖綁定。

視圖綁定生成的代碼是怎樣的

如前文所說,視圖綁定會(huì)生成一個(gè)包含替代 findViewById 功能的 Java 類。它會(huì)為 Module 下的每一個(gè)布局的 XML 文件生成一個(gè)對應(yīng)的綁定對象,并根據(jù)源文件為其命名,比如 activity_awesome.xml 對應(yīng)的綁定對象為 ActivityAwesomeBinding.java。

生成代碼的邏輯被優(yōu)化為,當(dāng)您在 Android Studio 中編輯 XML 布局文件時(shí),只會(huì)更新所修改布局對應(yīng)的綁定對象。同時(shí)這些工作會(huì)在內(nèi)存中運(yùn)行,從而使這個(gè)過程可以迅速完成。這意味著您的修改會(huì)立即反映在綁定對象中,而無須等待或者重新構(gòu)建工程。

Android Studio 被優(yōu)化為可以在您編輯過 XML 布局文件后立即更新綁定對象。

讓我們通過一個(gè)示例 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;

△ 視圖綁定生成的屬性。可以看到它們都是類型安全以及空安全的

視圖綁定會(huì)根據(jù)每個(gè)擁有 id 的視圖生成類型正確的屬性。他也會(huì)為根布局生成 rootView 屬性并通過 getRoot 暴露給您。視圖綁定沒有添加任何額外的邏輯,他只是把視圖屬性暴露給您,從而幫您在不使用 findViewById 的情況下也能調(diào)用它們。這樣一來便保證了生成文件簡潔性(當(dāng)然也避免了拖慢構(gòu)建速度)。

如果您正在使用 Kotlin,視圖綁定的生成類也已經(jīng)對互操作進(jìn)行了優(yōu)化。通過 @Nullable 和 @NonNull 注解的使用,Kolin 可以正確的將屬性暴露為空安全類型。如果想要了解更多關(guān)于兩種語言的互操作問題,請查閱文檔: 在 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);
  }

視圖綁定會(huì)生成 inflate 方法作為生成一個(gè)綁定對象實(shí)例的主要方式。在 ActivityAwesomeBinding.java 中,視圖綁定生成了一個(gè)只有一個(gè)參數(shù)的 inflate 方法,該方法通過將 parent 設(shè)定為空值來指定當(dāng)前視圖不會(huì)綁定到父視圖中;視圖綁定也暴露了一個(gè)有三個(gè)參數(shù)的 inflate 方法,來讓您在需要的時(shí)候傳入 parent 和 attachToParent 參數(shù)。

真正神奇的地方是 bind 方法的調(diào)用。這里會(huì)填充視圖并綁定所有的屬性,同時(shí)做一些錯(cuò)誤檢測并生成清晰的錯(cuò)誤提示。


 @NonNull
  public static ActivityAwesomeBinding bind(@NonNull View rootView) {
    /* 編輯: 簡化代碼 – 真實(shí)情況下生成的代碼是一個(gè)優(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 […]");
  }

△ 自動(dòng)生成的 bind 方法的簡化版本

bind 是綁定對象中最復(fù)雜的一個(gè)方法,它通過調(diào)用 findViewById 來綁定每個(gè)視圖。既然編譯器可以通過 XML 布局文件知道每個(gè)屬性的類型和為空的可能性,那他就可以安全的調(diào)用 findViewById。

請注意,視圖綁定生成的真正的 bind 方法要來的更長,并且其中使用了一個(gè)標(biāo)記 break 語句來優(yōu)化字節(jié)碼,您可以查看 Jake Wharton 撰寫的這篇文章來了解更多優(yōu)化有關(guān)的內(nèi)容。在每個(gè)綁定對象中,都會(huì)暴露三個(gè)靜態(tài)方法來創(chuàng)建綁定對象實(shí)例,下面是每個(gè)方法使用場景的簡要說明:

對使用 <include> 標(biāo)簽引入的布局會(huì)發(fā)生什么影響

前面已經(jīng)講過,視圖綁定會(huì)為 Module 下的每一個(gè)布局文件生成一個(gè)綁定對象,這個(gè)說法在布局文件被另一個(gè)布局文件使用 <include> 引入時(shí)依然適用。


<!-- 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 標(biāo)簽的示例 注意: include 標(biāo)簽下有一個(gè) id。

在使用引入布局的時(shí)候,視圖綁定會(huì)創(chuàng)建一個(gè)被引入布局綁定對象的引用。注意 <include> 標(biāo)簽有一個(gè) id: android:id="@+id/includes"。這里的邏輯跟使用普通視圖一樣, <include> 標(biāo)簽也需要有一個(gè) id 才能在綁定對象中生成對應(yīng)的屬性。

include 標(biāo)簽必須有一個(gè) id,才能生成對應(yīng)的屬性。

public final class ActivityAwesomeBinding implements ViewBinding {
  ...

  @NonNull
  public final IncludedButtonsBinding includes;

視圖綁定會(huì)在 ActivityAwesomeBinding 中生成一個(gè) IncludedButtonsBinding 的引用。

結(jié)合數(shù)據(jù)綁定來使用視圖綁定

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

在兩者都被開啟時(shí),使用 <layout> 標(biāo)簽的布局會(huì)由數(shù)據(jù)綁定來生成綁定對象;而其余的布局則由視圖綁定生成綁定對象。

您可以在同一 Module 中同時(shí)使用數(shù)據(jù)綁定和視圖綁定。

我們之所以開發(fā)視圖綁定作為數(shù)據(jù)綁定的補(bǔ)充,是因?yàn)樵S多開發(fā)者反映說,希望有一個(gè)輕量的解決方案,能在數(shù)據(jù)綁定之外替代 findViewById——視圖綁定提供的正是這一功能。

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

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

對于大多數(shù)應(yīng)用來說,我們推薦嘗試使用視圖綁定來替代這兩個(gè)庫,因?yàn)橐晥D綁定可以提供更加安全和準(zhǔn)確的視圖映射方式。


△ 視圖綁定空安全、只引用當(dāng)前布局中的視圖、支持 Java 和 Kotlin,同時(shí)也更簡潔

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

雖然 ButterKnife 會(huì)在運(yùn)行時(shí)校驗(yàn)可空與不可空,但是編譯器并不會(huì)檢查您匹配的視圖是否在存在于您的布局之中。

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

點(diǎn)擊這里了解更多有關(guān)視圖綁定的信息

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,973評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內(nèi)容