美文网首页Android开发Android开发经验谈Android开发干货集中营
View Binding 与Kotlin委托属性的巧妙结合,告别

View Binding 与Kotlin委托属性的巧妙结合,告别

作者: 依然范特稀西 | 来源:发表于2020-05-13 18:26 被阅读0次
    image

    前言

    最近看到一篇使用Kotlin委托属性来消除使用ViewBinding过程中样板代码的文章,觉得不错,因此翻译给大家,原文地址:

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

    image

    正文

    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

    相关文章

      网友评论

        本文标题:View Binding 与Kotlin委托属性的巧妙结合,告别

        本文链接:https://www.haomeiwen.com/subject/cpjonhtx.html