ViewBinding在Activity中的“终极”简便用法

353 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 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行代码已经是杀人诛心了。