Android中的IOC框架,完全注解方式就可以进行UI绑定和事件绑定

时间:2023-02-15 23:48:29

转载请注明出处:http://blog.csdn.net/blog_wang/article/details/38468547

相信很多使用过Afinal和Xutils的朋友会发现框架中自带View控件注解及
事件绑定功能,我们无需使用findViewById和setOnClickListener即可完成view初始化和监听事件,使用注解在很大程度上使
我们的代码看起来更加简洁,让我们的代码看起来不是那么冗余,那我们今天就来一探究竟,看看其中原理是如何来实现的。

Java注解相当于一种标记,标记可以加在包,类,字段,方法,方法的参数以及局部变量上。在程序中加入注解可以起到说明和配置的功能,Javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,根据你的标记,去做相应的事。

自定义注解

系统定义了许多注解类,但是都不满足我们的需求,那么我们想要实现注解的功能,就需要自定义注解类型,我们首先来了解自定义注解所需要知道的东西。

元注解是指注解的注解。包括  @Retention @Target @Document @Inherited四种

@Target
@Target(ElementType.TYPE)   //接口、类、枚举、注解
@Target(ElementType.FIELD) 
//字段、枚举的常量
@Target(ElementType.METHOD) //用于描述方法
@Target(ElementType.PARAMETER)
//用于描述参数
@Target(ElementType.CONSTRUCTOR) 
//用于描述构造器
@Target(ElementType.LOCAL_VARIABLE) //用于描述局部变量
@Target(ElementType.ANNOTATION_TYPE) //注解
@Target(ElementType.PACKAGE) 
//用于描述包

Target表示自定义的注解类型在什么地方使用,可以看出一个Target能使用多个ElementType,也就是说我们自定义的一个注解类型可以在字段、接口、包、方法上等多个地方使用,如:@Target({ElementType.FIELD,ElementType.METHOD}) 因此这个注解可以是字段注解,也可以是方法的注解

@Retention

@Retention(RetentionPolicy.RUNTIME) 
//在运行时有效
@Retention(RetentionPolicy.SOURCE) //在源文件中有效
@Retention(RetentionPolicy.CLASS) //在class文件中有效

Retention表示注解在什么范围内有效

@Document
Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员

@Inherited
Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

注解参数的可支持数据类型:

1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组

Annotation类型里面的参数该设定:
1.只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型
2.参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和
String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String
value();这里的参数成员就为String
3.如果只有一个参数成员,最好把参数名称设为"value",后加小括号

上面我们详细介绍了自定义注解类所能注解的类型、范围、注解参数的数据类型,以及注解在什么时期有效。OK,了解了那么多,那我们就来自己自定义一个注解类

  1. @Target(ElementType.FIELD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface InjectView{
  4. /**
  5. * 默认控件ID
  6. */
  7. public static int DEFAULT_ID = -1;
  8. /**
  9. * 默认方法
  10. */
  11. public static String DEFAULT_METHOD = "";
  12. /**
  13. * 功能:接收控件ID
  14. * @return 返回设置ID
  15. */
  16. public int id() default DEFAULT_ID;
  17. /**
  18. * 功能:接收控件点击方法名
  19. * @return 返回设置方法名
  20. */
  21. public String click() default DEFAULT_METHOD;
  22. }

根据@Target和@Retention我们可以看出这个注解类用于描述字段、枚举,在运行时有效,跟在方法后面的default字段表示当没有使用该参数注解时,调用该方法会返回的默认值,那一个定义好的注解我们要如何使用呢?

  1. public class MainActivity extends FrameActivity
  2. {
  3. /**
  4. * 指定控件对应ID和点击事件需要调用的方法名
  5. *
  6. * 这里的ID和click是自定义注解中属性名,多个参数用逗号分开
  7. *
  8. */
  9. @InjectView(id = R.id.button,click = "click")
  10. private Button button;
  11. @Override
  12. protected void onCreate(Bundle savedInstanceState)
  13. {
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.activity_main);
  16. //当走到这步时,button已经实例化了,并且可以相应点击事件,但是怎么做到的呢?
  17. button.setText("我是通过注解来实例化的");
  18. }
  19. public void click(View v){
  20. switch (v.getId()) {
  21. case R.id.button:
  22. Toast.makeText(this,"我被点击了",Toast.LENGTH_SHORT).show();
  23. break;
  24. default:
  25. break;
  26. }
  27. }
  28. }

如果大家眼睛比较亮的话,相信大家已经看出我不是继承至Activity,对,这里初始化的工作全部放在FrameActivity类里面,方便其他Activity使用,我们来看看FrameActivity里面都做了什么操作

  1. public class FrameActivity extends Activity{
  2. @Override
  3. public void setContentView(int layoutResID){
  4. super.setContentView(layoutResID);
  5. //在子类执行完setContentView方法之后调用
  6. traversalsField();
  7. }
  8. /**
  9. * 遍历类变量,获取变量注解
  10. */
  11. private void traversalsField(){
  12. //获取类所有属性,包括public,private,protected
  13. Field[] fields = getClass().getDeclaredFields();
  14. if(null != fields && fields.length > 0){
  15. for(Field field : fields){
  16. //判断属性注解是否属于自定义注解接口
  17. if(field.isAnnotationPresent(InjectView.class)){
  18. //获取变量注解类型
  19. InjectView injectView = field.getAnnotation(InjectView.class);
  20. //得到设置的ID
  21. int id = injectView.id();
  22. //如果获取的ID不等于默认ID,则通过findViewById来查找出对象然后设置变量值
  23. if(id != InjectView.DEFAULT_ID){
  24. try{
  25. //类中的成员变量为private,故必须进行此操作
  26. field.setAccessible(true);
  27. field.set(this,findViewById(id));
  28. }catch (IllegalArgumentException e){
  29. e.printStackTrace();
  30. }catch (IllegalAccessException e){
  31. e.printStackTrace();
  32. }
  33. }
  34. //得到设置方法名
  35. String method = injectView.click();
  36. if(!method.equals(InjectView.DEFAULT_METHOD)){
  37. setViewClickListener(this,field,method);
  38. }
  39. }
  40. }
  41. }
  42. }
  43. /**
  44. * 给View设置点击事件
  45. * @param injectedSource 类对象
  46. * @param field    属性
  47. * @param clickMethod 方法名
  48. */
  49. private void setViewClickListener(Object injectedSource,Field field,String clickMethod){
  50. try {
  51. //将属性转成Object类型
  52. Object obj = field.get(injectedSource);
  53. //判断Object类型是否是view的实例,如果是强转成view并设置点击事件
  54. if(obj instanceof View){
  55. ((View)obj).setOnClickListener(new EventListener(injectedSource).click(clickMethod));
  56. }
  57. } catch (Exception e) {
  58. e.printStackTrace();
  59. }
  60. }
  61. class EventListener implements OnClickListener{
  62. /**
  63. * 类对象
  64. */
  65. public Object obj;
  66. /**
  67. * 方法名
  68. */
  69. public String clickMethod;
  70. public EventListener(Object obj){
  71. this.obj = obj;
  72. }
  73. /**
  74. * click返回的是实现了OnClickListener接口的实例
  75. */
  76. public EventListener click(String clickMethod){
  77. this.clickMethod = clickMethod;
  78. return this;
  79. }
  80. //当view点击时会调用onClick方法
  81. @Override
  82. public void onClick(View v) {
  83. invokeClickMethod(obj, clickMethod, v);
  84. }
  85. private Object invokeClickMethod(Object obj, String methodName, Object... params){
  86. if(obj == null) {
  87. return null;
  88. }
  89. Method method = null;
  90. try{
  91. //获取类对象中以methodName和接受一个View参数的类型方法
  92. method = obj.getClass().getDeclaredMethod(methodName,View.class);
  93. if(method != null){
  94. //类中的方法为private,故必须进行此操作
  95. method.setAccessible(true);
  96. //执行方法,并传递当前对象
  97. return method.invoke(obj, params);
  98. }else{
  99. throw new Exception("no such method:" + methodName);
  100. }
  101. }catch(Exception e){
  102. e.printStackTrace();
  103. }
  104. return null;
  105. }
  106. }
  107. }


码中的注释还算是比较详细的,这里我就不在细说,大概说下实现原理,首先主要是获取当前类对象的所有属性包括public、private、
protected,然后循环判断属性是否采用注解并且是否是使用我们自定义的注解类,如果两种情况都满足则获取属性所指定的注解值,如果注解值不是默认
的就表示指定了明确的值,这里就可以根据获取到的ID值来findViewById()来获取对象,并且设置给当前属性来完成初始化。
指定控件的点击事件就比较麻烦,首先需要先将属性转成Object对象,然后判断是否是View的实例,如果是则设置点击事件,在
OnClickListener的onClick方法中获取当前类中以根据注解值来命名的方法,然后通过Method的invoke来调用执行,注意,这
里传递了事件对象本身,所以我们方法中必须要加入参数。
至此,我们所有的代码都已经写完了,我们运行起来看看效果

Android中的IOC框架,完全注解方式就可以进行UI绑定和事件绑定

源码下载,请点击这里

Android中的IOC框架,完全注解方式就可以进行UI绑定和事件绑定的更多相关文章

  1. Android 进阶 Android 中的 IOC 框架 【ViewInject】 (下)

    上一篇博客我们已经带大家简单的吹了一下IoC,实现了Activity中View的布局以及控件的注入,如果你不了解,请参考:Android 进阶 教你打造 Android 中的 IOC 框架 [View ...

  2. Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (下)

    上一篇博客我们已经带大家简单的吹了一下IoC,实现了Activity中View的布局以及控件的注入,如果你不了解,请参考:Android 进阶 教你打造 Android 中的 IOC 框架 [View ...

  3. 【转】Android中的IOC框架,完全注解方式就可以进行UI绑定和事件绑定

    转载请注明出处:http://blog.csdn.net/blog_wang/article/details/38468547 相信很多使用过Afinal和Xutils的朋友会发现框架中自带View控 ...

  4. Android 进阶Android 中的 IOC 框架 【ViewInject】 (上)

    1.概述 首先我们来吹吹牛,什么叫IoC,控制反转(Inversion of Control,英文缩写为IoC),什么意思呢? 就是你一个类里面需要用到很多个成员变量,传统的写法,你要用这些成员变量, ...

  5. Android 进阶 教你打造 Android 中的 IOC 框架 【ViewInject】 (上)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/39269193,本文出自:[张鸿洋的博客] 1.概述 首先我们来吹吹牛,什么叫Io ...

  6. android中的http框架,使其更加简单易用

    Afinal 是一个android的sqlite orm 和 ioc 框架. Afinal 是一个android的sqlite orm 和 ioc 框架.同时封装了android中的http框架,使其 ...

  7. Spring框架(3)---IOC装配Bean(注解方式)

    IOC装配Bean(注解方式) 上面一遍文章讲了通过xml来装配Bean,那么这篇来讲注解方式来讲装配Bean对象 注解方式需要在原先的基础上重新配置环境: (1)Component标签举例 1:导入 ...

  8. 跟着刚哥学习Spring框架--通过注解方式配置Bean(四)

    组件扫描:Spring能够从classpath下自动扫描,侦测和实例化具有特定注解的组件. 特定组件包括: 1.@Component:基本注解,识别一个受Spring管理的组件 2.@Resposit ...

  9. Spring总结四:IOC和DI 注解方式

    首先我们要了解注解和xml配置的区别: 作用一样,但是注解写在Bean的上方来代替我们之前在xml文件中所做的bean配置,也就是说我们使用了注解的方式,就不用再xml里面进行配置了,相对来说注解方式 ...

随机推荐

  1. Android -- 启动另外一个Activity的方式(2s自动启动)

    1.  使用Handler  并且可以设置进入和退出的动画效果 Class < ? > activityClass; Class [ ] paramTypes = { Integer.TY ...

  2. img标签 加载FTP的图片 C&num;

    好吧,我是菜鸟,这是我今天遇到的问题,什么也不会,得高人指点 1.使用FtpWebRequest下载图片,以流存贮 2.在ashx文件里面直接已流方式(HttpContext.Current.Resp ...

  3. ES5特性之Object&period;freeze

    Object.freeze方法比Object.seal方法更严格,不仅不能扩展新对象和不可重新配置属性的特性,还不能改变对象属性的值writable(不可写)

  4. CF &num;365 &lpar;Div&period; 2&rpar; D - Mishka and Interesting sum 离线树状数组(转)

    转载自:http://www.cnblogs.com/icode-girl/p/5744409.html 题目链接:CF #365 (Div. 2) D - Mishka and Interestin ...

  5. Light OJ 1018 - Brush &lpar;IV&rpar;

    题目大意:     一个二维平面上有N个点,一把刷子,刷一次可以把一条线上的所有点都刷掉.问最少刷多少次,可以把全部的点都刷完 状态压缩DP, 用记忆化搜索来写, 需要有个优化不然会超时. ===== ...

  6. Express 学习记录

    1. Express 4.0以上的版本需要独立安装 the express "generator",即 npm install -g express-generator.

  7. 一台nginx服务器多域名配置 (转)

    Nginx强大的正则表达式支持,可以使server_name的配置变得很灵活,如果你要做多用户博客,那么每个用户拥有自己的二级域名也就很容易实现了. 下面我就来说说server_name的使用吧: s ...

  8. 设计模式 笔记 策略模式 Strategy

    //---------------------------15/04/28---------------------------- //Strategy 策略模式----对象行为型模式 /* 1:意图 ...

  9. Docker inside Docker 基于 Alpine Linux

    Study From https://hub.docker.com/_/docker/ 感慨一句 这些人真牛B .. 简单测试 拉取镜像 docker pull docker:dind 运行镜像 do ...

  10. P3173 &lbrack;HAOI2009&rsqb;巧克力 &amp&semi;&amp&semi; P1324 矩形分割

    题目描述 出于某些方面的需求,我们要把一块N×M的木板切成一个个1×1的小方块. 对于一块木板,我们只能从某条横线或者某条竖线(要在方格线上),而且这木板是不均匀的,从不同的线切割下去要花不同的代价. ...