作者:HiDhl
來源:https://www.gushiciku.cn/pl/gfG8/zh-tw
正文開始
前言
公眾號:ByteCode,致力於分享最新技術原創文章,涉及 Kotlin、Jetpack、譯文、系統原始碼、 LeetCode / 劍指 Offer / 多執行緒 / 國內外大廠演算法題 等等一系列文章。
首先祝小夥伴們新年快樂,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 倉庫: github.com/hi-dhl/Bind…
這篇文章可以理解為對之前的文章 Kotlin 外掛的落幕,ViewBinding 的崛起 的一個補充,從使用的角度分析了 DataBinding 和 ViewBinding 不同之處,同時也介紹瞭如何用更簡單的方式實現 DataBinding 和 ViewBinding。