携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情
前言
这个“终极”而又“无聊”的用法源于我的一个Android开发的朋友。他最近在接触Kotlin,忽然问我,黄油刀在kt怎么用?我一脸蒙蔽,好像我还真的不知道,因为……我一早就抛弃黄油刀了。我跟他说用ViewBinding啊还用什么黄油刀!过一会儿我问他,怎么样,ViewBinding香吗?他竟然说一般,不能写进BaseActivity,黄油刀的注解不香吗?Excuse me?当你一个布局有100个控件的时候,黄油刀就要写100次,但ViewBinding无论多少个控件都只要两行代码。虽然是说服了这位兄dei,但我却开始思考:ViewBinding是不是真的不可以写进BaseActivity里呢?结果一顿捣鼓还真让我写进去了。
正文开始
终极1.0
为啥一开始会觉得ViewBinding不能写进BaseActivity里呢?那是因为ViewBinding会为每一个布局文件按照其名称自动生成一个Binding类,也就是说每一个Activity的Binding类都不一样,那怎么能写进父类Activity里呢?
好吧其实还是有机会的,毕竟,反射为所欲为!虽说每一个Binding类都是不一样的,但是命名规则是确定的——与布局文件的名称对应。具体的对应关系为:布局文件名称中按下划线划分单词,首字母大写后去除所有下划线并在后面加上"Binding"。也就是说activity_main.xml对应的Binding类为ActivityMainBinding。
而正常情况下,Activity与其布局文件是有着类似的命名联系的(只要你是从Android Studio新建Activity的方式而不是分开创建Activity的话)。那就可以利用这个命名联系进行反射获取Binding类。话不多说先上代码:
open class BaseActivity: AppCompatActivity() {
//因为从ViewBinding生成的Binding类看出所有Binding类都实现了ViewBinding接口
//所以使用了ViewBinding作为BaseActivity中binding的类型
lateinit var binding: ViewBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//获取实际子类的名称
val acName = javaClass.simpleName
//去除后面的“Activity”
val name = acName.substring(0,acName.indexOf("Activity"))
//拼接前面的“Activity”以及后面的“Binding”,并通过完整包名进行反射获取实际Binding类
val bindingClass = classLoader.loadClass("com.example.test.databinding.Activity${name}Binding")
//invoke反射调用Binding类中的inflate方法获取到Binding类实例并赋值给全局变量binding
binding = bindingClass.getMethod("inflate", LayoutInflater::class.java).invoke(null,layoutInflater) as ViewBinding
}
}
这样子就完成了将ViewBinding写进BaseActivity的需求了。
使用如下:
class MainActivity : BaseActivity() {
//声明ActivityMainBinding
private lateinit var activityMainBinding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//将BaseActivity中的binding强转成ActivityMainBinding
activityMainBinding = binding as ActivityMainBinding
//关联布局
setContentView(activityMainBinding.root)
}
}
嗯?可是这样子,跟直接用有什么区别呢??还不是2行代码吗??还写多了BaseActivity中的代码……
好吧!继续改进!
终极2.0
既然这样子,我连这两行代码都不要了!这时候我想到了泛型。BaseActivity改进如下:
//这里主要的不同之处在于需要传入实际使用的Binding的类型
open class BaseActivity<BindingClass> : AppCompatActivity() {
//私有化的Binding类,类型即为实际使用的Binding类型
private var innerBinding: BindingClass? = null
//暴露给子类的Binding类,返回innerBinding的非空类型主要是为了避免子类使用
//的时候需要频繁加上!!(如有更好的解决方法请务必告诉我)
val binding: BindingClass by lazy { innerBinding!! }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//以下操作基本相同
val acName = javaClass.simpleName
val name = acName.substring(0, acName.indexOf("Activity"))
val bindingClass =
classLoader.loadClass("com.example.test.databinding.Activity${name}Binding")
//最后强转为以泛型传入的实际Binding的类型
innerBinding = bindingClass.getMethod("inflate", LayoutInflater::class.java)
.invoke(null, layoutInflater) as BindingClass
}
}
大功告成!在子类Activity中的用法简化为:
class MainActivity : BaseActivity<ActivityMainBinding>() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//关联布局(直接使用BaseActivity的binding即可)
setContentView(binding.root)
}
}
这样子的话,只要继承于BaseActivity的Activity,就可以直接使用父类中的binding,连声明和赋值的2行代码都省了!绝对终极!
后言
虽说这样的做法终极简便,但纯当一个玩笑就好了。毕竟在真正的项目中如果这样做的话不知道会不会有什么“意外惊喜”。其实啊,ViewBinding已经足够简便了,面对无数行的findViewById和无数的牛油刀,2行代码已经是杀人诛心了。