实现类似ButterKnife 为控制绑定id
前言
之前写的第一篇文章 Android APT的学习和使用(一)初步了解kotlin下怎么编写注解并自动生成代码,这次继续实践一下,实现类似BufferKnife 在字段前添加注解自动为我们完成绑定id的功能,在此记录一下。
一、概览
1. 想要实现的效果
我们使用注解,在里面传入控件的id,然后就会自动为我们绑定控件,我们在代码里就不用再写findViewById(R.id.xx)这样的代码了.
2. 项目结构
因为是在我这个打算代代相传的demo项目里编写的,所以上一篇文章的项目结构一致
3.使用kotlinpoet生成的最终代码
这个代码需要实现的功能、名字、传入的参数等等都要提前想好,保证能够正确执行,最好先自己建个类,然后手动调用该类,确保能够准确执行了,然后再将该代码作为模板,在BindViewProcessor中用kotlinpoet生成代码。
public class AptActivityBinding {
public fun bind(activity: AptActivity): Unit {
activity.textView1 = activity.findViewById(2131230798)
activity.button1 = activity.findViewById(2131230797)
}
}
二、实现
1. 定义注解
因为是在字段上使用,所以Target用AnnotationTarget.FIELD
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FIELD)
annotation class BindView(val value: Int)
2. 继承AbstractProcessor处理注解并生成我们想要的代码
@AutoService(Processor::class)
@SupportedOptions("MODULE_NAME")
@SupportedAnnotationTypes("com.wzy.apt_annotation.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class BindViewProcessor : AbstractProcessor() {
private var mFiler: Filer? = null
override fun init(processingEnvironment: ProcessingEnvironment?) {
super.init(processingEnvironment)
processingEnvironment?.apply {
mFiler = filer
}
}
override fun process(p0: MutableSet<out TypeElement>?, p1: RoundEnvironment?): Boolean {
if (p0.isNullOrEmpty()) return false
// 构建一个kt文件存放生成的类
val ktFile = FileSpec.builder("com.wzy.demo", "BindViewAutoCreateFile")
//获取全部的类
p1?.rootElements?.forEach {
//获取类的包名
val packageName = it.enclosingElement.toString()
//获取类的名字
val className = it.simpleName.toString()
//构建新的类: 原类名+Binding
val newClassName = ClassName(packageName,"${className}Binding")
//构建bind(activity: XXXActivity)函数
val bindFuncitonBuilder = FunSpec.builder("bind")
.addModifiers(KModifier.PUBLIC)
.returns(Unit::class)
.addParameter("activity", ClassName(packageName, className))
//判断该类下是否使用了BindView注解从而判断是否需要生成代码
var skip = true
it.enclosedElements.forEach { enclosedElemnet ->
if (enclosedElemnet.kind == ElementKind.FIELD) {
enclosedElemnet.getAnnotation(BindView::class.java)?.let { bindViewAnnotation ->
skip = false
bindFuncitonBuilder.addStatement(
"activity.%N = activity.findViewById(%L)",
enclosedElemnet.simpleName,bindViewAnnotation.value
)
}
}
}
//是否跳过生成代码
if (!skip) {
//构建新的类
val classBuilder = TypeSpec.classBuilder(newClassName)
.addFunction(bindFuncitonBuilder.build())
//往BindViewAutoCreateFile.kt文件中写入该类
ktFile.addType(classBuilder.build())
}
}
try {
mFiler?.let {
ktFile.build().writeTo(it)
}
} catch (e: Exception) {
e.printStackTrace()
}
return true
}
}
3. 编译项目
查看是否生成我们想要的代码
代码已经生成好了,只要传入activity对象,我们就可以拿到View,然后手动调用findViewById()方法为他们绑定id。
4. 封装Api
上面说到我们需要调用生成的XXXActivityBinding的bind()方法,并且传入activity就算完成。这种操作肯定不需要我们自己去写,不然每个Activity会生成自己的Binding类,每次调用都要new一个XXXActivityBinding类,然后还要调用该方法。虽然说不是不可以,但是ButterKnife只需要在执行一行代码ButterKnife.bind(this)就可以完成自动绑定,根本不需要管是哪个类,这种api调用明显方便的多。
所以我们可以写一个api,利用反射帮我们去new XXXActivityBinding,然后执行bind()方法。
object ButterKnifeApi {
fun bind(activity: Activity) {
try {
//获取注解为我们生成的Binding类
val activityBinding = Class.forName("${activity.packageName}.${activity.javaClass.simpleName}Binding")
//拿到bind()方法
val bindFunction = activityBinding.getMethod("bind", activity.javaClass)
//调用该方法
bindFunction.invoke(activityBinding.newInstance(), activity)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
使用和ButterKnife一样,调用ButterKnifeApi.bind(this)就可以了。
5.示例
class AptActivity : AppCompatActivity() {
@BindView(R.id.aptTextView1)
lateinit var textView1: TextView
@BindView(R.id.aptButton1)
lateinit var button1: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_apt)
initViewByBinding()
}
private fun initViewByBinding() {
ButterKnifeApi.bind(this)
button1.setOnClickListener {
textView1.text = "绑定成功"
}
}
}