这几年针对Android推出了不少View注入框架,例如ButterKnife。我们首先来了解一下使用这些框架有什么好处,其实好处很明显:它可以减少大量的findViewById以及setOnClickListener代码,简化了代码,让我们的代码看起来条理更清晰,可读性变强。

     可能大多数对于这一类框架,都只是停留在用的阶段,但是作为一个程序员,我们有必要去了解它是如何实现的。其实它的原理也没有多复杂,用到了Java中反射和注解的相关知识,所以对反射和注解了解不多的朋友可以先找一下相关资料了解一下。

关于注解,我在上一篇文章中介绍过

今天的任务呢就是教大家如何一步一步简单的实现一个类似 ButterKnife的依赖注入的效果:

第一步:我们需要自定义注解,如下:

//用于初始化view的

1. @Target(ElementType.FIELD)  
2. @Retention(RetentionPolicy.RUNTIME)  
3. public @interface BindView {  
4. int value() default 0;  
5. }

//用于绑定点击事件的

    1. @Target(ElementType.METHOD)  
    2. @Retention(RetentionPolicy.RUNTIME)  
    3. public @interface BindClick {  
    4. int value() default 0;  
    5. }

    从上面的Target里面的值我们可以知道,这两个注解一个是作用于属性,一个是作用于方法的。这里提醒一下 ,如果如果只有一个参数成员,最好把参数名称设为value,这里可以补一句因为使用该注解时,value作为key可省略,在使用的时候比较方便。

    第二步:设置注解

    1. @BindView(R.id.button)  
    2. private Button mButton;  
    3. @BindView(R.id.textview)  
    4. private TextView mTextView;

    1. @BindClick(R.id.button)  
    2. private void onButtonClick(){  
    3. this,"Button被点击了",Toast.LENGTH_SHORT).show();  
    4. }  
    5. @BindClick(R.id.textview)  
    6. private void onTextViewClick(){  
    7. this,"TextView被点击了",Toast.LENGTH_SHORT).show();  
    8. }

    第三步:获取注解,进行处理

      1. public class InjectUtils {  
      2.  /**
       *注入视图
       */
      public static void initView(Activity injectedActivity){
          initView(injectedActivity,injectedActivity.getWindow().getDecorView());
      }
      /**
       *注入视图
       */
      public static void initView(YYBaseViewController injectedViewController){
          initView(injectedViewController,injectedViewController.getView());
      }
      /**
       *注入视图
       */
      public static void initView(Object injectedSource,View sourceView){
          Field[] fields = injectedSource.getClass().getDeclaredFields();
          if(isNotNull(fields) && fields.length>0){
              for(Field field : fields){
                  YYInjectView viewInject = field.getAnnotation(YYInjectView.class);
                  if(isNotNull(viewInject)){
                      int viewId = viewInject.id();
                      try {
                          field.setAccessible(true);
                          if(field.get(injectedSource)!= null ){
                              //如果有值就屏蔽,例如float,int等类型
                              continue;
                          }
                          field.set(injectedSource,sourceView.findViewById(viewId));
                      }catch (Exception e){
                          e.printStackTrace();
                      }
                  }
              }
          }
      } 
      3. public static void inject(final Activity activity) {  
      4. //遍历属性,对设置注解的view进行初始化  
      5.         Class<Activity> activityClass= (Class<Activity>) activity.getClass();  
      6.         Field fields[]=activityClass.getDeclaredFields();  
      7. for(Field field:fields){  
      8. if(field.isAnnotationPresent(BindView.class)){  
      9. int viewId=field.getAnnotation(BindView.class).value();  
      10.                 View view=activity.findViewById(viewId);  
      11. try {  
      12. //这一行代码是必须的,否则直接调用set方法不生效  
      13. true);  
      14.                     field.set(activity,view);  
      15. catch (IllegalAccessException e) {  
      16.                     e.printStackTrace();  
      17.                 }  
      18.             }  
      19.         }  
      20. //遍历方法 将设置注解的方法绑定到相应的view的点击事件中  
      21.         Method methods[]=activityClass.getDeclaredMethods();  
      22. for(final Method method:methods){  
      23. class);  
      24. if(bindClick!=null){  
      25. int viewId=bindClick.value();  
      26. new View.OnClickListener() {  
      27. @Override  
      28. public void onClick(View view) {  
      29. try {  
      30. //调用该方法  
      31. true);  
      32.                             method.invoke(activity);  
      33. catch (IllegalAccessException e) {  
      34.                             e.printStackTrace();  
      35. catch (InvocationTargetException e) {  
      36.                             e.printStackTrace();  
      37.                         }  
      38.                     }  
      39.                 });  
      40.             }  
      41.         }  
      42.     }  
      43. }

      第四步:其实上面已经完成了处理,我们只需要调用一下就可以了,像下面那样:

      1. setContentView(R.layout.activity_main);  
      2. //必须放在setContentView后面  
      3. InjectUtils.inject(this);