天天看點

竟然如此簡單,DataBinding 和 ViewBinding

竟然如此簡單,DataBinding 和 ViewBinding

前言

首先祝小夥伴們新年快樂,2020 一個不平凡的一年,2021 是你我新的起點。

2021 新簽名:代碼不止,文章不停。

2021 第一篇文章是對 2020 年末最後一篇文章 Kotlin 插件的落幕,ViewBinding 的崛起 的一個補充。

在之前的文章 Kotlin 插件的落幕,ViewBinding 的崛起 中介紹了 Google 為什麼不建議在項目中使用 Kotlin 合成方法(Synthetic 視圖), Google 建議使用 ViewBinding 替換 Kotlin 合成方法,那麼 ViewBinding 和 DataBinding 都有什麼差別。

ViewBinding:

  • 僅僅支援綁定 View
  • 不需要在布局檔案中添加 layout 标簽
  • 需要在子產品級

    build.gradle

    檔案中添加

    viewBinding = true

    即可使用
  • 效率高于 DataBinding,因為避免了與資料綁定相關的開銷和性能問題
  • 相比于

    kotlin-android-extensions

    插件避免了空異常

DataBinding:

  • 包含了 ViewBinding 所有的功能
  • 需要在子產品級

    build.gradle

    檔案内添加

    dataBinding = true

    并且需要在布局檔案中添加 layout 标簽才可以使用
  • 支援 data 和 view 雙向綁定
  • 效率低于 ViewBinding,因為注釋處理器會影響資料綁定的建構時間。

ViewBinding 可以實作的, DataBinding 都可以實作,但是 DataBinding 的性能低于 ViewBinding,DataBinding 和 ViewBinding 會為每個 XML 檔案生成綁定類。

R.layout.activity_main -> ActivityMainBinding
R.layout.fragment_main -> FragmentMainBinding
R.layout.dialog_app -> DialogAppBinding
           

在 Kotlin 插件的落幕,ViewBinding 的崛起 文章中同時也分析了 Kotlin 合成方法所帶來的問題。即使 Kotlin 合成方法有很多問題,但是還有小夥伴願意使用。

ViewBinding 和 DataBinding 為我們解決了這麼多問題,但是為什麼很多小夥伴們不願意使用 ViewBinding 和 DataBinding,今天我們從使用的角度來分析。

ViewBinding 和 DataBinding

我大概彙總了 ViewBinding 和 DataBinding 在不同場景的所有用法,我們來看一下在項目中如何使用。

基本配置

Android Studio 3.6

版本開始,就内置在 Gradle 插件中了,不需要添加任何額外的庫來使用它們,但是在

Android Studio 3.6

Android Studio 4.0

中使用方式不一樣。

// Android Studio 3.6
android {
    viewBinding {
        enabled = true
    }
    dataBinding{
        enabled = true
    }
}

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

ViewBinding 的使用

因為涉及到的場景比較多,為了減少篇幅,我隻列出來核心部分,如果之前從來沒有用過,這裡隻需要知道 ViewBinding 的門檻比 Kotlin 合成方法要高即可。

不想為某個布局生成 binding 類,将下面屬性添加到布局檔案的根視圖中

<LinearLayout tools:viewBindingIgnore="true" >
</LinearLayout>
           

在 Activity 中使用

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding: ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
}
           

在 Fragment 中使用

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    val binding = FragmentViewBindBinding.inflate(inflater,container,false)
    return binding.root
}
           

在 Adapter 中的使用

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    RecycleItemProductBinding.inflate(LayoutInflater.from(parent.context), parent, false)
}
           

在 Dialog 中使用

override fun onCreate(savedInstanceState: Bundle?) {
    binding = DialogAppBinding.inflate(layoutInflater)
    setContentView(binding.root)
}
           

include 标簽的使用

include

标簽不帶

merge

标簽,需要給

include

标簽添加 id, 直接使用 id 即可,用法如下所示。

<include
    android:id="@+id/include"
    layout="@layout/layout_include_item" />
    
val binding: ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)
binding.include.includeTvTitle.setText("使用 include 布局中的控件, 不包含 merge")
           

include

标簽帶

merge

标簽,注意這裡和 DataBinding 用法不一樣,給

include

标簽添加 id,在 DataBinding 中可以直接使用 id,ViewBinding 則不行,ViewBinding 的用法如下所示。

<include layout="@layout/layout_merge_item" />

val binding: ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)
val mergeItemBinding = LayoutMergeItemBinding.bind(binding.root)
mergeItemBinding.mergeTvTitle.setText("使用 include 布局中的控件, 包含 merge")
           

ViewStub 标簽的使用

根據實踐證明,截止到這篇文章釋出時,在

Android Studio 4.2.0 bata 2

中,無法直接在 ViewBinding 布局中使用

ViewStub

标簽,僅僅隻能在 DataBinding 布局(帶

layout

标簽)中使用,詳見 issue

因為沒有找到比較權威的資料證明,這裡建議小夥們直接在項目 Binding 中進行嘗試,如果有其他在 ViewBinding 布局中的實作方式,歡迎留言告知我

DataBinding 的使用

因為涉及到的場景比較多,為了減少篇幅,我隻列出來核心部分,如果之前從來沒有用過,這裡隻需要知道 DataBinding 的門檻比 Kotlin 合成方法要高即可。

需要給布局檔案添加 layout 标簽

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout...>
    ...
    </LinearLayout
</layout>
           

在 Activity 中使用

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    binding.lifecycleOwner = this
    setContentView(binding.root)
}
           

在 Fragment 中使用

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
    val binding = FragmentViewBindBinding.inflate(inflater,container,false)
    binding.lifecycleOwner = this
    return binding.root
}
           

在 Adapter 中的使用

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
    val bidning:RecycleItemProductBinding =  DataBindingUtil.bind(view) 
}
           

在 Dialog 中使用

override fun onCreate(savedInstanceState: Bundle?) {
    binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.dialog_data_binding, null, false)
    setContentView(binding.root)
}
           

include 标簽的使用

include

标簽不帶

merge

标簽,需要給

include

标簽添加 id, 直接使用 id 即可。

<include
    android:id="@+id/includeData"
    layout="@layout/layout_include_data_item"/>
    
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.includeData.includeTvTitle.setText("通過代碼設定 include layout 的控件")
           

include

标簽帶

merge

标簽,注意這裡和 ViewBinding 用法不一樣,給

include

标簽添加 id,在 DataBinding 中可以直接使用,在 ViewBinding 中則不行。

<include
    android:id="@+id/includeDataMerge"
    layout="@layout/layout_merge_data_item"/>

val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.includeDataMerge.mergeTvTitle.setText("通過代碼設定 merge layout 的控件")
           

ViewStub 标簽的使用

ViewStub

标簽添加 id, 在 DataBinding 中可以直接使用 id 即可。

<ViewStub
    android:id="@+id/stub"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout="@layout/view_stub" />

binding.stub.setOnInflateListener { stub, inflated ->
    // ViewBinding
    val viewStub: ViewStubBinding = ViewStubBinding.bind(inflated)
    viewStub.tvTitle.setText("使用 ViewStub 加載 ViewBinding 布局")
}

binding.stub.setOnInflateListener { stub, inflated ->
    // DataBinding
    val dataViewStub: ViewStubDataBinding = DataBindingUtil.bind(inflated)!!
    dataViewStub.tvTitle.setText("使用 ViewStub 加載 DataBinding 布局")
}
                
if (!binding.stub.isInflated) {
    binding.stub.viewStub!!.inflate()
}   
           

正如你所見,在

Ativity

Fragment

Dialog

Adapter

include

merge

ViewStub

等等場景中,使用 ViewBinding 或者 DataBinding 都要進行不同的處理,相比于 Kotlin 合成方法,這使用門檻太高了。

那麼能不能用一種方法,可以統一這些初始化方案,在 Kotlin 中僅僅需要一行代碼即可實作 DataBinding 和 ViewBinding。

一行代碼

如果在每個場景中都需要手動進行不同的處理,這樣的成本是非常大的,是以我推出了一個新庫 Binding ,Binding 結合 Kotlin 委托屬性,統一封裝了 DataBinding 和 ViewBinding 不同的處理, 提供了簡單的 API 如下所示。

ViewBinding 中的使用

val binding: ActivityViewBindBinding by viewbind()
           

DataBinding 中的使用

val binding: ActivityDataBindBinding by databind(R.layout.activity_data_bind)
或者
val binding: ActivityDataBindBinding by databind()
           

正如你所見,隻需要簡單的幾個 API 即可實作上述所有場景,我們先來介紹一下 Binding。

Binding 未來的規劃提供通用的

findViewById

解決方案,因技術的疊代更新從

butterknife

DataBinding

、 Kotlin 合成方法(Synthetic 視圖)到現在 ViewBinding , 未來也有可能出現新的技術,無論技術怎麼變化,隻要 Binding 對外的使用保持不變,隻需要更新 Binding ,即可完成遷移。

Binding 具有以下優點:

  • 提供了很多實戰案例包含

    Ativity

    Fragment

    Dialog

    Adapter

    include

    merge

    ViewStub

    Navigation

    、 資料雙向綁定 等等場景
  • 簡單的 API 隻需要一行代碼即可實作 DataBinding 或者 ViewBinding
  • 支援在

    Activity

    AppCompatActivity

    FragmentActivity

    Fragment

    Dialog

    中的使用 DataBinding 或者 ViewBinding
  • 支援在

    ListAdapter

    PagedListAdapter

    PagingDataAdapter

    RecyclerView.Adapter

    中的使用 DataBinding 或者 ViewBinding
  • 支援在 Navigaion Fragment 管理架構、 BottomSheetDialogFragment 等等場景中使用 DataBinding 和 ViewBinding
  • 避免大量的模闆代碼
  • 避免記憶體洩露,具有生命周期感覺能力,當生命周期處于

    onDestroyed()

    時會自動銷毀資料

接下來我們一起來分析一下如何在項目中使用 Binding,将下列代碼添加在子產品級

build.gradle

檔案内,并且需要開啟 DataBinding 或者 ViewBinding。

dependencies {
    implementation 'com.hi-dhl:binding:1.0.7'
}
           

Activity

AppCompatActivity

FragmentActivity

中使用,添加

by viewbind()

或者

by databind(R.layout.activity_main)

即可,示例如下所示。

class MainActivity : AppCompatActivity() {

    // DataBinding
    val binding: ActivityMainBinding by databind(R.layout.activity_main)
    
    // ViewBinding
    val binding: ActivityMainBinding by viewbind()
}
           

Fragment

中提供了兩種方式:

  • 方式一:在

    onCreateView

    中使用,這種方式适用于所有使用

    Fragment

    的場景
  • 方式二:在

    onViewCreated

    中使用

方式一:

class FragmentNav1 : Fragment(R.layout.fragment_main) {
    
    // DataBinding
  	val binding: FragmentMainBinding by databind()
    
    // ViewBinding
  	 val binding: FragmentMainBinding by viewbind()
  
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return binding.root
    }
}
           

方式二,需要注意以下幾點:

  • 不能在

    Navigaion Fragment

    BottomSheetDialogFragment

    中使用
  • 在其他 Fragment 場景中,如果使用

    方式二

    界面不顯示,改用

    方式一

    即可解決
class FragmentNav1 : Fragment(R.layout.fragment_main) {
    
    // DataBinding
  	val binding: FragmentMainBinding by databind()
    
    // ViewBinding
  	 val binding: FragmentMainBinding by viewbind()
  
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.apply { textView.setText("Binding") }
    }
}
           

Dialog

中使用方式如下所示。

class AppDialog(context: Context) : Dialog(context, R.style.AppDialog) {

    // DataBinding
    val binding: DialogAppBinding by databind(R.layout.dialog_data_binding)
    
    // ViewBinding
    val binding: DialogAppBinding by viewbind()
}
           

或者添加具有生命周期感覺的

Dialog

class AppDialog(context: Context,lifecycle: Lifecycle) : Dialog(context, R.style.AppDialog) {

    // DataBinding 監聽生命周期
    val binding: DialogAppBinding by databind(R.layout.dialog_data_binding, lifecycle)
    
    // ViewBinding 監聽生命周期
    val binding: DialogAppBinding by viewbind(lifecycle)
    
}
           

在 Adapter 中使用 DataBinding 和 ViewBinding,隻需要在 ViewHolder 中添加

by viewbind()

或者

by databind()

即可,示例如下所示。

class ProductViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    
    // DataBinding
    val binding: RecycleItemProductBinding by databind()

    // ViewBinding
    val binding: RecycleItemProductHeaderBinding by viewbind()

}
           

擴充方法,支援 DataBinding 初始化的時候綁定資料。

val binding: ActivityDataBindBinding by databind(R.layout.activity_data_bind) {
    val account = Account()
    account.name = "test"
    this.account = account
}
           

上面隻是常見的幾種用法,當然還有更多實戰案例(

include

merge

ViewStub

Navigation

、 資料雙向綁定 等等)已經上傳到 GitHub 歡迎前去倉庫 Binding 檢視。

GitHub 倉庫: https://github.com/hi-dhl/Binding

這篇文章可以了解為對之前的文章 Kotlin 插件的落幕,ViewBinding 的崛起 的一個補充,從使用的角度分析了 DataBinding 和 ViewBinding 不同之處,同時也介紹了如何用更簡單的方式實作 DataBinding 和 ViewBinding。

全文到這裡就結束了,如果有幫助 點個贊 就是對我最大的鼓勵 代碼不止,文章不停 持續分享最新的技術

最後推薦我一直在更新維護的項目和網站:

  • 全新系列視訊:現代 Android 開發 (MAD) 技巧系列教程:線上檢視
  • 計劃建立一個最全、最新的 AndroidX Jetpack 相關元件的實戰項目 以及 相關元件原理分析文章,正在逐漸增加 Jetpack 新成員,倉庫持續更新,歡迎前去檢視:AndroidX-Jetpack-Practice
  • LeetCode / 劍指 offer / 國内外大廠面試題 / 多線程 題解,語言 Java 和 kotlin,包含多種解法、解題思路、時間複雜度、空間複雜度分析
    竟然如此簡單,DataBinding 和 ViewBinding
    • 劍指 offer 及國内外大廠面試題解:線上閱讀
    • LeetCode 系列題解:線上閱讀
  • 最新 Android 10 源碼分析系列文章,了解系統源碼,不僅有助于分析問題,在面試過程中,對我們也是非常有幫助的,倉庫持續更新,歡迎前去檢視 Android10-Source-Analysis
  • 整理和翻譯一系列精選國外的技術文章,每篇文章都會有譯者思考部分,對原文的更加深入的解讀,倉庫持續更新,歡迎前去檢視 Technical-Article-Translation
  • 「為網際網路人而設計,國内國外名站導航」涵括新聞、體育、生活、娛樂、設計、産品、營運、前端開發、Android 開發等等網址,歡迎前去檢視 為網際網路人而設計導航網站

繼續閱讀