自己实现Butterknife

1 篇文章 0 订阅
1 篇文章 0 订阅

Butterknife优缺点

  • 优点:通过apt技术在编译期生成findViewById和OnClick代码,简化代码,提高效率
  • 缺点:略微增加编译时间

实现效果

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv_one)
    TextView tv_one;
    @BindView(R.id.btn_one)
    Button btn_one;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Butterknife.bind(this);


    }

    @OnClick({R.id.btn_one,R.id.tv_one})
    public void click(View v){
        Toast.makeText(this,"11",Toast.LENGTH_SHORT).show();
    }

}

先写注解

最好新建一个JavaLibrary填入注解,此例注解library包名com.zrsoft.annotationlib

@BindView注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value();
}

@OnClick注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnClick {
    int[] value();

}

创建一个AndroidLibrary创建ButterKnife类

 本例通过泛型接口实现 此Library包名com.zrsoft.library

@ViewBinder

public interface ViewBinder<T> {
    void bind(T target);
}

ButterKnife

public class Butterknife {
    //通过反射获取ViewBinder子类并执行ViewBinder的bind方法
    public static void bind(Object o) {
        String className = o.getClass().getName() + "$ViewBinder";
        try {
            Class<?> clazz = Class.forName(className);
            ViewBinder viewBinder = (ViewBinder) clazz.newInstance();
            viewBinder.bind(o);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

定义一个ReOnClickListener方法,方便调用OnClick注解的方法

public abstract class ReOnClickListener implements View.OnClickListener{
    @Override
    public void onClick(View v) {
        doClick(v);

    }

    public abstract void doClick(View v);
}

最后新建一个注解处理JavaLibrary

包名com.zrsoft.comlib

在build中引入javapoet和谷歌官方的注解处理服务,并导入注解所在的library

    implementation 'com.squareup:javapoet:1.10.0'
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
    implementation project(path: ':annotationlib')//注解所在library

新建ButterknifeProcessor类继承AbstractProcessor

ButterknifeProcessor


@AutoService(Processor.class)
//@SupportedAnnotationTypes()//要处理的注解 和重写getSupportedAnnotationTypes方法效果一样
@SupportedSourceVersion(SourceVersion.RELEASE_7)//什么版本的JDK进行编译
public class ButterknifeProcessor extends AbstractProcessor {
    private Messager messager;//不要打印ERROR会直接结束
    private Elements elementsUtils;
    private Filer filer;
    private Types typesUtils;

    List<TypeElement> activityNames;//含有所要遍历注解的类的集合

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        elementsUtils = processingEnv.getElementUtils();
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();

        activityNames = new ArrayList<>();
        messager.printMessage(Diagnostic.Kind.NOTE, "init---------------------");
    }

    //可能调用多次但只有一次有数据
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        messager.printMessage(Diagnostic.Kind.NOTE, "process--------------------");
        activityNames.clear();

        //遍历BindView注解字段并存入map
        Set<? extends Element> bindViewSet = roundEnv.getElementsAnnotatedWith(BindView.class);
        Map<String, List<VariableElement>> bindViewMap = new HashMap<>();
        for (Element element : bindViewSet) {

            VariableElement variableElement = (VariableElement) element;
            String activityName = getActivityName(variableElement);
            if (!bindViewMap.containsKey(activityName)) {
                bindViewMap.put(activityName, new ArrayList<VariableElement>());
            }
            bindViewMap.get(activityName).add(variableElement);

        }
        //遍历OnClick注解方法并存入map
        Set<? extends Element> clickViewSet = roundEnv.getElementsAnnotatedWith(OnClick.class);
        Map<String, List<ExecutableElement>> clickViewMap = new HashMap<>();
        for (Element element : clickViewSet) {

            ExecutableElement executableElement = (ExecutableElement) element;
            String activityName = getActivityName(executableElement);
            if (!clickViewMap.containsKey(activityName)) {
                clickViewMap.put(activityName, new ArrayList<ExecutableElement>());
            }
            clickViewMap.get(activityName).add(executableElement);

        }

        //遍历含有注解的类的集合生成代码(以下简称注解类)
        for (TypeElement activityName : activityNames) {
            //包名
            String packageName = activityName.getQualifiedName().toString().replace(
                    "." + activityName.getSimpleName().toString(), "");
            //注解类全名
            String qualifiedName = activityName.getQualifiedName().toString();
            //注解类简称
            String simpleName = activityName.getSimpleName().toString();
            //生成类简称
            String newSimpleName = simpleName + "$ViewBinder";


            messager.printMessage(Diagnostic.Kind.NOTE, "start--------------------" +  newSimpleName);

            //注解类
            ClassName className = ClassName.get(packageName, simpleName);

            //生成类方法
            MethodSpec.Builder bind = MethodSpec.methodBuilder("bind")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addParameter(className, "target", Modifier.FINAL)
                    .addAnnotation(Override.class);

            //遍历此类中注解BindView的属性,生成findViewById代码
            for (VariableElement variableElement : bindViewMap.get(qualifiedName)) {
                //属性名称
                String fieldName = variableElement.getSimpleName().toString();
                //注解value
                int annValue = variableElement.getAnnotation(BindView.class).value();
                //属性类型
                TypeName fileType = ClassName.get(variableElement.asType());
                //写入代码
                bind.addStatement("$N." + fieldName + "=($T)$N.findViewById($L)", "target", fileType, "target", annValue);

            }
            //遍历此类中注解OnClick的属性,生成setOnClickListener代码
            for (ExecutableElement executableElement : clickViewMap.get(qualifiedName)) {
                //方法名称
                String fieldName = executableElement.getSimpleName().toString();
                //注解value
                int[] annValue = executableElement.getAnnotation(OnClick.class).value();
                //自定义点击事件类
                ClassName clicklistener = ClassName.get("com.zrsoft.library", "ReOnClickListener");
                //View类
                ClassName view = ClassName.get("android.view", "View");
                //生成匿名内部类
                TypeSpec bindm = TypeSpec.anonymousClassBuilder("")
                        .addSuperinterface(clicklistener)
                        .addMethod(MethodSpec.methodBuilder("doClick")
                                .addAnnotation(Override.class)
                                .addModifiers(Modifier.PUBLIC)
                                .addParameter(view, "v")
                                .returns(void.class)
                                .addStatement(" $N.$N($N)", "target", fieldName, "v")
                                .build())
                        .build();
                bind.addStatement("$L $N = $L",clicklistener,"clicklistener",bindm);


                //写入代码
                for (int i : annValue) {
                    bind.addStatement("$N.findViewById($L).setOnClickListener($N)", "target", i, "clicklistener");
                }

            }
            //所要实现的接口
            ClassName interfaceName = ClassName.get("com.zrsoft.library", "ViewBinder");
            //实现接口<泛型>
            ParameterizedTypeName typeName = ParameterizedTypeName.get(interfaceName, className);
            //写类
            TypeSpec typeBuild = TypeSpec.classBuilder(newSimpleName)
                    .addModifiers(Modifier.PUBLIC)
                    .addSuperinterface(typeName)
                    .addMethod(bind.build())
                    .build();


            JavaFile javaFile = JavaFile.builder(packageName, typeBuild)
                    .build();


            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {

                e.printStackTrace();
            }

        }


        return false;
    }

    /**
     * 找到元素所在的类 并加入集合
     *
     * @param element
     * @return
     */
    private String getActivityName(Element element) {
        Element e = element;
        while (!e.getKind().equals(ElementKind.CLASS)) {
            e = e.getEnclosingElement();
        }
        TypeElement te = (TypeElement) e;

        if (!activityNames.contains(te)) {
            activityNames.add(te);
            messager.printMessage(Diagnostic.Kind.NOTE, "activity--------" + te.getQualifiedName().toString());
        }
        return te.getQualifiedName().toString();
    }

    /**
     * 所要遍历的注解
     *
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }
}

生成的代码类

public class MainActivity$ViewBinder implements ViewBinder<MainActivity> {
  @Override
  public void bind(final MainActivity target) {
    target.tv_one=(TextView)target.findViewById(2131165327);
    target.btn_one=(Button)target.findViewById(2131165218);
    com.zrsoft.library.ReOnClickListener clicklistener = new ReOnClickListener() {
      @Override
      public void doClick(View v) {
         target.click(v);
      }
    };
    target.findViewById(2131165218).setOnClickListener(clicklistener);
    target.findViewById(2131165327).setOnClickListener(clicklistener);
  }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值