为什么推荐DataBinding或ViewBinding代替你的FindViewById
从ButterKnife到kotlin-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
其实DataBinding比ViewBinding多了数据绑定的作用,如果不需要用到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仅仅作为位置控制,没有实际调用的地方,而ViewBinding和DataBinding都会把这些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.*
特别是Fragment和DialogFragment一旦内部方法 this.getView() 拿回来是null,这里第一条就会崩了。
第二条是写成 getView()?.view?.xxxx = xxx
个人还是建议多去对比新旧工具的区别,没有绝对的完美,就像DataBinding,到现在还是很多人诟病他在XML中写逻辑或者Bean中写逻辑,但是它带来的数据绑定确实方便了不少,特别是双向绑定。