文章首發於公眾號「 技術最TOP 」
正文
ViewBinding 是Android Studio 3.6中新增的一個新功能,更準確的說,它是DataBinding 的一個更輕量變體,為什麼要使用View Binding 呢?答案是效能。許多開發者使用Data Binding庫來引用Layout XML中的檢視,而忽略它的其他強大功能。相比來說,自動生成程式碼ViewBinding其實比DataBinding 效能更好。但是傳統的方式使用View Binding 卻不是很好,因為會有很多樣板程式碼(垃圾程式碼)。
View Binding 的傳統使用方式
讓我們看看Fragment 中“ViewBinding”的用法。我們有一個佈局資源profile.xml
。View Binding 為佈局檔案生成的類叫ProfileBinding
,傳統使用方式如下:
class ProfileFragment : Fragment(R.layout.profile) {
private var viewBinding: ProfileBinding? = null
override fun onViewCreated(view: View, savedState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewBinding = ProfileBinding.bind(view)
// Use viewBinding
}
override fun onDestroyView() {
super.onDestroyView()
viewBinding = null
}
}
有幾點我不太喜歡:
- 建立和銷燬
viewBinding
的樣板程式碼 - 如果有很多Fragment,每一個都要拷貝一份相同的程式碼
viewBinding
屬性是可空的,並且可變的,這可不太妙
怎麼辦呢?用強大Kotlin來重構它。
Kotlin 委託屬性結合ViewBinding
使用Kotlin委託的屬性,我們可以重用部分程式碼並簡化任務(不明白委託屬性的,可以看我(譯者)以前的文章:一文徹底搞懂Kotlin中的委託),我用它來簡化·ViewBinding的用法。用一個委託包裝了
ViewBinding`的建立和銷燬。
class FragmentViewBindingProperty<T : ViewBinding>(
private val viewBinder: ViewBinder<T>
) : ReadOnlyProperty<Fragment, T> {
private var viewBinding: T? = null
private val lifecycleObserver = BindingLifecycleObserver()
@MainThread
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
checkIsMainThread()
this.viewBinding?.let { return it }
val view = thisRef.requireView()
thisRef.viewLifecycleOwner.lifecycle.addObserver(lifecycleObserver)
return viewBinder.bind(view).also { vb -> this.viewBinding = vb }
}
private inner class BindingLifecycleObserver : DefaultLifecycleObserver {
private val mainHandler = Handler(Looper.getMainLooper())
@MainThread
override fun onDestroy(owner: LifecycleOwner) {
owner.lifecycle.removeObserver(this)
viewBinding = null
}
}
}
/**
* Create new [ViewBinding] associated with the [Fragment][this]
*/
@Suppress("unused")
inline fun <reified T : ViewBinding> Fragment.viewBinding(): ReadOnlyProperty<Fragment, T> {
return FragmentViewBindingProperty(DefaultViewBinder(T::class.java))
}
然後,使用我們定義的委託來重構ProfileFragment
:
class ProfileFragment : Fragment(R.layout.profile) {
private val viewBinding: ProfileBinding by viewBinding()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Use viewBinding
}
}
很好,我們去掉了建立和銷燬ViewBinding的樣板程式碼,現在只需要宣告一個委託屬性就可以了,是不是簡單了?但是現在還有點問題。
問題來了
在重構之後,onDestroyView
需要清理掉viewBinding中的View。
class ProfileFragment() : Fragment(R.layout.profile) {
private val viewBinding: ProfileBinding by viewBinding()
override fun onDestroyView() {
super.onDestroyView()
// Clear data in views from viewBinding
// ViewBinding inside viewBinding is null
}
}
但是,結果是,我得到的在委託屬性內對ViewBinding的引用為null
。原因是Fragment的ViewLifecycleOwner
通知更新lifecycle的ON_DESTROY
事件時機,該事件發生在Fragment.onDestroyView()
之前。這就是為什麼我僅在主執行緒上的所有操作完成後才需要清除viewBinding。可以使用Handler.post
完成。修改如下:
class FragmentViewBindingProperty<T : ViewBinding>(
private val viewBinder: ViewBinder<T>
) : ReadOnlyProperty<Fragment, T> {
private var viewBinding: T? = null
private val lifecycleObserver = BindingLifecycleObserver()
@MainThread
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
checkIsMainThread()
this.viewBinding?.let { return it }
val view = thisRef.requireView()
thisRef.viewLifecycleOwner.lifecycle.addObserver(lifecycleObserver)
return viewBinder.bind(view).also { vb -> this.viewBinding = vb }
}
private inner class BindingLifecycleObserver : DefaultLifecycleObserver {
private val mainHandler = Handler(Looper.getMainLooper())
@MainThread
override fun onDestroy(owner: LifecycleOwner) {
owner.lifecycle.removeObserver(this)
// Fragment.viewLifecycleOwner call LifecycleObserver.onDestroy() before Fragment.onDestroyView().
// That's why we need to postpone reset of the viewBinding
mainHandler.post {
viewBinding = null
}
}
}
}
這樣,就很完美了。
Android的新庫ViewBinding是一個去掉專案中findViewByid()
很好的解決方案,同時它也替代了著名的Butter Knife
。ViewBinding 與Kotlin委託屬性的巧妙結合,可以讓你的程式碼更加簡潔易讀。完整的程式碼可以檢視github:https://github.com/kirich1409...