[关闭]
@cxm-2016 2016-08-16T11:34:36.000000Z 字数 2982 阅读 2212

Kotlin语言使用反射机制编写运行时View注入

android
作者:陈小默
kotlin版本:1.0.3


Kotlin是一种基于JVM的语言,这种语言最令人着迷的一点的就能与Java语言互相调用。于是有些人就以为按照Java的用法就能写好Koltin的程序。可是,Kotlin毕竟也是一门独立的高级语言呀。其内部一些机制和Java还是有很大区别的。现在我们通过编写一个运行时View注入的例子来了解一下Kotlin的反射。
首先我们先定义一个注解:

  1. @Retention(RetentionPolicy.RUNTIME)
  2. @Target(ElementType.FIELD)
  3. annotation class BindView(val value: Int)

这里我们首先定义一个类BaseViewHolder,这个类用来处理View的注入事件。因为该类要被继承,所以这里应该加上open关键字。

  1. open class BaseViewHolder {
  2. fun bind(activity: Activity) {
  3. }
  4. }

在类里我们添加了一个bind方法,这个方法的目的是用来实现注入操作,我们最后再来实现这个方法。

接下来我们实现一个MyViewHolder去继承BaseViewHolder

  1. class MyViewHolder : BaseViewHolder() {
  2. @BindView(R.id.name) var name: TextView? = null
  3. @BindView(R.id.phone) var phone: TextView? = null
  4. @BindView(R.id.email) var email: TextView? = null
  5. @BindView(R.id.show) var show: Button? = null
  6. }

布局文件我就不说了,自行创建更改上面的id
接下来在Activity中我们调用注入方法并显示输入信息

  1. class MainActivity : AppCompatActivity() {
  2. private var holder = MyViewHolder()
  3. override fun onCreate(savedInstanceState: Bundle?) {
  4. super.onCreate(savedInstanceState)
  5. setContentView(R.layout.activity_main)
  6. holder.bind(this)
  7. holder.show?.setOnClickListener { v ->
  8. Toast.makeText(this@MainActivity,
  9. """姓名:${holder.name?.text.toString()}
  10. |手机:${holder.phone?.text.toString()}
  11. |邮箱:${holder.email?.text.toString()}""".trimMargin(), Toast.LENGTH_SHORT).show()
  12. }
  13. }
  14. }

好了,现在解释一下上述代码:第10行字符串表示方式是"""原生字符串"""里面的内容可以跨行且不需要转义,第11行和第12行前面的|代表前导字符,表示这一行从这里开始前面的空白字符都忽略不计。

目前我们的整个工程就算写好了。现在我们要回过头去实现注入方法,考虑一下,如果使用Java的反射机制应该如何实现注入。。。。。。嗯,应该是这个样子:

  1. fun bind(activity: Activity) {
  2. val any = this
  3. this.javaClass.declaredFields.forEach { field ->
  4. field.isAccessible = true
  5. val ann = field.getAnnotation(BindView::class.java)
  6. if (ann != null) {
  7. val view = activity.findViewById(ann.value)
  8. field.set(any, view)
  9. }
  10. }
  11. }

我们运行一下试试。。。。。。没反应,对不对。这是因为Kotlin和Java采用的反射机制不同,而我们的MyViewHolder并不是Java文件我们看到的class.java是为了Kotlin能够调用Java文件增加的类信息。但是我们Kotlin文件的交互还要通过Java去进行操作吗?所以我们要使用Kotlin内部的反射机制。

不知道是我没有找到还是因为当前1.0.3版本不完整,我并没有找到属性操作的相关方法,但是我通过查看源码扩展了常用的方法,第一个是任何类型都可以使用的获得Koltin类信息的方法,第二个是获得相应类型的全部注解(java8允许一个属性存在多个同类型的注解)第三个就是我们常用的获得获得属性上一个注解,随后一个方法是扩展的属性set方法

  1. fun Any.kotlin() = this.javaClass.kotlin
  2. fun KProperty1<*, *>.getAnnotations(type: KClass<*>): List<Annotation>? {
  3. return this.annotations.filter { ann ->
  4. ann.annotationClass == type
  5. }.toList()
  6. }
  7. fun <T : Annotation> KProperty1<*, *>.getAnnotation(type: KClass<T>): T? {
  8. val list = getAnnotations(type)
  9. return if (list != null) {
  10. list[0] as T
  11. } else {
  12. null
  13. }
  14. }
  15. fun KProperty1<Any, Any?>.set(any: Any, value: Any?) {
  16. (this as KMutableProperty1<Any, Any?>).set(any, value)
  17. }

将这些扩展置于同级别包下即可

现在我们再来实现注入方法

  1. fun bind(activity: Activity) {
  2. val any = this
  3. any.kotlin().memberProperties.forEach { mem ->
  4. mem.isAccessible = true
  5. val ann = mem.getAnnotation(BindView::class)
  6. if (ann != null) {
  7. val view = activity.findViewById(ann.value)
  8. mem.set(any, view)
  9. }
  10. }
  11. }

现在一个Kotlin的注解就成了
这里有其他更好的方法:

  1. @Retention(AnnotationRetention.RUNTIME)
  2. @Target(AnnotationTarget.FIELD)
  3. annotation class BindView(val value: Int)
  4. open class BaseViewHolder {
  5. fun bind(activity: Activity) {
  6. val any = this
  7. any.javaClass.declaredFields.forEach { field ->
  8. field.isAccessible = true
  9. val ann = field.getAnnotation(BindView::class.java)
  10. if (ann != null) {
  11. val view = activity.findViewById(ann.value)
  12. field.set(any, view)
  13. }
  14. }
  15. }
  16. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注