View Binding 與Kotlin委託屬性的巧妙結合,告別垃圾代碼!

前言

最近看到一篇使用Kotlin委託屬性來消除使用ViewBinding過程中樣板代碼的文章,覺得不錯,因此翻譯給大家,原文地址:

https://proandroiddev.com/make-android-view-binding-great-with-kotlin-b71dd9c87719

正文

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/ViewBindingPropertyDelegate

以上就是本文的全部內通,歡迎關注的我公衆號:「技術最TOP」,每天都有技術優質文章推出,最前沿的科技動態,最TOP的編程技術一網打盡。

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