推荐ViewBinding或DataBinding代替你的FindViewById

1,688 阅读3分钟

为什么推荐DataBinding或ViewBinding代替你的FindViewById

ButterKnifekotlin-android-extensions,再到Android Studio 3.6的ViewBinding,其实这些都是工具帮我们解放FindViewById的写法。ViewBinding的作用是每个xml文件生成一个绑定类,绑定类的实例包含对在相应布局中具有 ID 的所有视图的直接引用,实际里面也是用到了FindViewById,但是工具帮我们生成好了。

如何调用

在项目build.gradle中引入

android{
    buildFeatures{
        viewBinding true
        dataBinding true
    }
    viewBinding{
        enabled = true
    }
    dataBinding{
        enabled = true
    }
}

通过buildFeatures或者单独设置都可以。

ViewBinding对比DataBinding

其实DataBindingViewBinding多了数据绑定的作用,如果不需要用到DataBinding的数据绑定,单独使用ViewBinding即可。

一键工具

日常开发中,如果是两种都引用,生成两种不同类,区别在xml布局文件是否有 <layout>,这里直接贴代码,一键获取。

@Throws(
        NoSuchMethodException::class,
        InvocationTargetException::class,
        IllegalAccessException::class
    )
    fun <V : ViewBinding> getBinding(any: Any, view: View,typeIndex:Int): V? {
        var binding: V? = null
        val types = (any.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
        val clazz = types[typeIndex] as Class<V>
        if (ViewBinding::class.java.isAssignableFrom(clazz) && !ViewDataBinding::class.java.isAssignableFrom(clazz)) {
            val method = clazz.getDeclaredMethod("bind", View::class.java)
            binding = method.invoke(clazz, view) as V?
        } else if (ViewDataBinding::class.java.isAssignableFrom(clazz)) {
            binding = DataBindingUtil.bind<ViewDataBinding>(view) as V?
        }
        return binding
    }


    inline fun <reified V : ViewBinding> binding(view: View): V? {
        val clazz = V::class.java
        return if (ViewBinding::class.java.isAssignableFrom(clazz)
            && !ViewDataBinding::class.java.isAssignableFrom(clazz)
        ) {
            /**
             * For ViewBinding Only
             */
            val method = clazz.getDeclaredMethod("bind", View::class.java)
            val binding = method.invoke(clazz, view) as V?
            binding
        } else {
            /**
             * For ViewDataBinding Only
             */
            DataBindingUtil.bind<ViewDataBinding>(view) as V?
        }
    }
   

第二种方法不解释了,第一种方法适用于利用 基类<T,T,T> 的童鞋,在子类中找到这个泛型 如这样:

class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>

typeIndex就是你第一个需要的即0 (注意混淆问题导致方法丢失)

总结

对比传统的FindViewById,少了强转的问题。当然AndroidX的童鞋会发现FindViewById变成了findViewById<T extends View>(),相对而言也好用来很多了,后面不用写一堆as。这时就会有童鞋说我们平时开发用到约束布局ConstraintLayout,有很多Id仅仅作为位置控制,没有实际调用的地方,而ViewBindingDataBinding都会把这些Id生成一个对应的局部变量帮我们写好FindViewById很浪费,我们其实看问题不能只扣一个点来看,没有任何工具都是完美的,那先问一句,你这些局部变量最终生成到哪里?是直接写到你的Acitivity,Fragment的局部变量吗?而是用一个类帮你们装好。帮你们简化了Acitivity,Fragment局部变量里不用写一大堆View,代码也简洁了。如果使用工具生成FindViewById也是差不多这样的写到了Acitivity,Fragment

如果在Kotlin环境下工具利用到委托那还好:

val view by lazy{ bindView(R.id.xxx) }

有些工具生成有缺陷会出现这样:

子类:
@bind(R.id.xxx)
lateinit var view:TextView

override fun onCreate(savedInstanceState: Bundle?){
     super.onCreate(savedInstanceState)
     //生成绑定
     BindView.bind(this)
}

fun initView(){
  view.text = "内容"
}

------------------

而你这个子类的父类中onCreate是这样

override fun onCreate(savedInstanceState: Bundle?){
     super.onCreate(savedInstanceState)
     setContentView(getlayoutId())
     initView()
}

如果工具生成这样,你看看崩不崩溃,直接跑。 如果不是生成lateinit var,而是var view:TextView? = null,那你看看是不是调用时候一堆调用问号,或者双感叹号。

kotlin-android-extensions的确很好用,特别是刚刚接触Kotlin的童鞋,但是你们有发现吗?写多了会发现偶尔就出现自己import引入错了对应的布局,还有你们有发现这两个有区别吗?
import kotlinx.android.synthetic.main.xxxxx.*
import kotlinx.android.synthetic.main.xxxxx.view.*
特别是FragmentDialogFragment一旦内部方法 this.getView() 拿回来是null,这里第一条就会崩了。 第二条是写成 getView()?.view?.xxxx = xxx

个人还是建议多去对比新旧工具的区别,没有绝对的完美,就像DataBinding,到现在还是很多人诟病他在XML中写逻辑或者Bean中写逻辑,但是它带来的数据绑定确实方便了不少,特别是双向绑定