我们希望在类上通过注解的方式,指定Activity的布局
1.新建注解该注解需要一个布局id的参数
@Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class ContentView(val value: Int)2.定义注入工具类
通过反射获取ContentView注解,并最终调用Activity的setContentView方法
object InjectUtils { fun inject(activity: baseActivity) { injectContentView(activity) } private fun injectContentView(activity: baseActivity) { activity.javaClass.getAnnotation(ContentView::class.java)?.apply { activity.setContentView(value) } } }3.Activity创建时,调用注入工具
写一个基类,在onCreate中调用注入工具的方法
open class baseActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) //运行时注入 InjectUtils.inject(this) } }4.Activity类上使用注解
我们继承baseActivity基类,并使用ContentView注解指定布局id
@ContentView(R.layout.activity_main) class MainActivity : baseActivity() { }
布局就一个默认的TextView,内容如下:
效果:
二、控件id绑定有了上面的基础,控件id绑定也是依葫芦画瓢
1.控件绑定注解@Target(AnnotationTarget.FIELD) @Retention(AnnotationRetention.RUNTIME) annotation class BindID(val id: Int)2.注入工具实现
object InjectUtils { fun inject(activity: baseActivity) { injectContentView(activity) injectViewId(activity) } private fun injectContentView(activity: baseActivity) { activity.javaClass.getAnnotation(ContentView::class.java)?.apply { activity.setContentView(value) } } private fun injectViewId(activity: baseActivity) { activity.javaClass.declaredFields.filter { field -> field.getAnnotation(BindID::class.java) != null }.forEach{ field -> field.apply { isAccessible = true set(activity, activity.findViewById(getAnnotation(BindID::class.java)!!.id)) } } } }3.Activity中使用注解
@ContentView(R.layout.activity_main) class MainActivity : baseActivity() { @BindID(R.id.tv_hello) val tvHello: TextView? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) tvHello?.apply { text = "hello inject" } } }
效果:
三、事件注入事件注入需要使用动态代理,我们需要生成View对应的事件回调(点击、长按等)匿名类对象
1.定义事件元注解为了方便扩展,我们定义一个元注解,来表示事件注解需要代理的设置监听方法、监听事件接口、接口方法,如:setOnClickListener,View.OnClickListener::class,onClick
@Target(AnnotationTarget.ANNOTATION_CLASS) @Retention(AnnotationRetention.RUNTIME) annotation class Event( val setter: String, val listenerClz: KClass2.定义事件注解, val listenerCallbackMethodName: String )
事件注解需要使用元注解,注明代理控件的设置监听方法、监听方法传入的参数类型、监听类的回调函数名。
还需要一个集合属性,用来获取需要绑定的控件id集合
@Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) @Event( setter = "setOnClickListener", listenerClz = View.OnClickListener::class, listenerCallbackMethodName = "onClick" ) annotation class onClick( vararg val ids: Int )3.注入工具实现
我们需要获取Activity中使用OnClick注解的方法,并获取OnClick注解的元注解Event,通过元注解,获取控件的setOnClickListener方法,并通过动态代理生成View.OnClickListener的代理对象,最后通过反射调用setOnClickListener方法为view绑定代理对象
object InjectUtils { fun inject(activity: baseActivity) { injectContentView(activity) injectViewId(activity) injectClick(activity) } private fun injectContentView(activity: baseActivity) { activity.javaClass.getAnnotation(ContentView::class.java)?.apply { activity.setContentView(value) } } private fun injectViewId(activity: baseActivity) { activity.javaClass.declaredFields.filter { field -> field.getAnnotation(BindID::class.java) != null }.forEach { field -> field.apply { isAccessible = true set(activity, activity.findViewById(getAnnotation(BindID::class.java)!!.id)) } } } private fun injectClick(activity: baseActivity) { //获取方法 activity.javaClass.declaredMethods.filter { method -> //过滤非OnClick注解 method.getAnnotation(OnClick::class.java) != null }.forEach { method -> //method 为 Activity中的clickView方法 //获取OnClick注解 val onClick = method.getAnnotation(OnClick::class.java) //获取控件id数组 val ids = onClick!!.ids //获取OnClick注解的 元注解:Event method.annotations.forEach { //强转成Java Annotation对象,因为kotlin无法获取元注解(注解的注解) (it as java.lang.annotation.Annotation).annotationType() .getAnnotation(Event::class.java) ?.apply { //给每个View绑定事件 ids.forEach { id -> val view: View = activity.findViewById(id) //获取setOnClickListener方法,入参为View.onClickListener val setonClickListenerMethod = view.javaClass.getMethod(setter, listenerClz.java) //绑定 点击事件回调函数名onClick 与 Activity中的clickView方法(被OnClick注解的方法) 的关系 val map = mapOf(listenerCallbackMethodName to method) //动态代理 val handler = ClickInvocationHandler(WeakReference(activity), map) //动态代理生成的对象:点击事件匿名内部类 val proxy = Proxy.newProxyInstance( listenerClz.java.classLoader, arrayOf(listenerClz.java), handler ) //为view设置setonClickListener setOnClickListenerMethod.invoke(view, proxy) } } } } } }4.动态代理
InvocationHandler中,我们需要代理View.OnClickListener的onClick方法,改为调用被OnClick注解的方法,通过外部传入的Map,可以通过方法名快速获取到被OnClick注解的方法
class ClickInvocationHandler( private val activity: WeakReference5.封装, private val map: Map ) : InvocationHandler { override fun invoke(proxy: Any?, method: Method?, args: Array ?): Any? { //发现Method是onClick方法,执行被OnClick注解的clickView方法 method?.apply { activity.get()?.run { //onClick回调函数的入参为view: View?,将它强转成View后再传给clickView方法 return map[name]?.invoke(this, args!![0] as View) } } return method?.invoke(proxy, args) } }
我们将注入事件的方法优化,使它更具扩展性,将注解类型作为参数传入,并将控件id集合通过lambda获取
object InjectUtils { fun inject(activity: baseActivity) { injectContentView(activity) injectViewId(activity) injectClick(activity, OnClick::class.java) { it.ids } } private fun injectContentView(activity: baseActivity) { activity.javaClass.getAnnotation(ContentView::class.java)?.apply { activity.setContentView(value) } } private fun injectViewId(activity: baseActivity) { activity.javaClass.declaredFields.filter { field -> field.getAnnotation(BindID::class.java) != null }.forEach { field -> field.apply { isAccessible = true set(activity, activity.findViewById(getAnnotation(BindID::class.java)!!.id)) } } } private inline funinjectClick( activity: baseActivity, clz: Class , getIds: (annotation: T) -> IntArray ) { //获取方法 activity.javaClass.declaredMethods.filter { method -> //过滤非OnClick注解 method.getAnnotation(clz) != null }.forEach { method -> //method 为 Activity中的clickView方法 //获取OnClick注解 val onClick = method.getAnnotation(clz) //获取控件id数组 val ids = getIds(onClick) //获取OnClick注解的 元注解:Event method.annotations.forEach { //强转成Java Annotation对象,因为kotlin无法获取元注解(注解的注解) (it as java.lang.annotation.Annotation).annotationType() .getAnnotation(Event::class.java) ?.apply { //给每个View绑定事件 ids.forEach { id -> val view: View = activity.findViewById(id) //获取setOnClickListener方法,入参为View.onClickListener val setonClickListenerMethod = view.javaClass.getMethod(setter, listenerClz.java) //绑定 点击事件回调函数名onClick 与 Activity中的clickView方法(被OnClick注解的方法) 的关系 val map = mapOf(listenerCallbackMethodName to method) //动态代理 val handler = ClickInvocationHandler(WeakReference(activity), map) //动态代理生成的对象:点击事件匿名内部类 val proxy = Proxy.newProxyInstance( listenerClz.java.classLoader, arrayOf(listenerClz.java), handler ) //为view设置setonClickListener setOnClickListenerMethod.invoke(view, proxy) } } } } } }
这样如果我们想要新增长按事件功能,只需要新增长按事件的注解,并再次调用注入事件方法即可
6.Activity中使用注解@ContentView(R.layout.activity_main) class MainActivity : baseActivity() { @BindID(R.id.tv_hello) val tvHello: TextView? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) tvHello?.apply { text = "hello inject" } } @onClick(R.id.tv_hello, R.id.tv_hello2) fun clickView(view: View) { Toast.makeText(this, (view as TextView).text, Toast.LENGTH_SHORT).show() } }
效果:
项目地址:https://gitee.com/aruba/iocapplication.git欢迎分享,转载请注明来源:内存溢出
评论列表(0条)