AnnotationProcessor实战:实现ButterKnife的setOnClickListener方法

有了前一篇文章的理解,其实这个功能并不会太难

1.如何定义注解BindClick

    @BindClick(R.id.test2 )
    public void showToast(){
        Toast.makeText(MainActivity.this, "test", Toast.LENGTH_SHORT).show();
    }
    //最终实现
    findViewById(R.id.test2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        showToast();
      }
    });
//butterknife里面增加了DebouncingOnClickListener,就是为了去抖动,防止网络请求过慢,导致用户再次点击而请求两次网络数据
    findViewById(R.id.test2).setOnClickListener(new DebouncingOnClickListener() {
      @Override
      public void doClick(View view) {
        host.showToast();
      }
    });

butterknife里面的DebouncingOnClickListener不难理解,就不贴了
由此可以看到,这次需要传参的就很多,而且明显是一个监听器的类,所以需要定义一个ListenerClass,同时里面还需要一个ListenerMethod
最终的BindClick

@ListenerClass(
        targetType = "android.view.View",
        setter = "setOnClickListener",
        type = "com.company.libapi.util.DebouncingOnClickListener",
        method = @ListenerMethod(
                name = "doClick",
                parameters = "android.view.View"
        )
)
public @interface BindClick {
    int value();
}

2.如何获取标注了BindClick的成员信息

和之前BindView一样
添加ClickBinder接口

public interface ClickBinder {
    void bindClick(T host, Object object);

    void unBindClick(T host);
}

在LCJViewBinder中获取并且管理(重要代码)

private static void bindClick(Object host, Object object) {
        String className = host.getClass().getName();
        try {
            ClickBinder binder = clickBinderMap.get(className);
            if (binder == null) {
                Class aClass = Class.forName(className + "$$ClickBinder");
                binder = (ClickBinder) aClass.newInstance();

                clickBinderMap.put(className, binder);
            }
            if (binder != null) {
                binder.bindClick(host, object);//这里最终会在AbstractProcessor中生成具体的实现代码
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

用BindClickField来记录每个BindClick的成员变量

BindClickField(Element element) throws IllegalArgumentException {
        if (element.getKind() != ElementKind.METHOD) {
            throw new IllegalArgumentException(String.format("Only methods can be annotated with @%s",
                    BindClick.class.getSimpleName()));
        }
        mVariableElement = (Symbol.MethodSymbol) element;

        BindClick bindClick = mVariableElement.getAnnotation(BindClick.class);
        mResId = bindClick.value();
        mListenerClass = bindClick.annotationType().getAnnotation(ListenerClass.class);
        if (mResId < 0) {
            throw new IllegalArgumentException(
                    String.format("value() in %s for field %s is not valid !", BindClick.class.getSimpleName(),
                            mVariableElement.getSimpleName()));
        }
    }

用AnnotatedClick来保存BindClickField(其中generateFile函数后面再说)
那这些BindClickField是怎么去找的呢?就是LCJClickBinderProcessor

3.如何生成setOnClickListener方法对应的代码呢?

就是AnnotatedClick的generateFile函数

    JavaFile generateFile() throws Exception{
        //generateMethod
        MethodSpec.Builder bindViewMethod = MethodSpec.methodBuilder("bindClick")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class)
                .addParameter(TypeName.get(mTypeElement.asType()), "host", Modifier.FINAL)
                .addParameter(TypeName.OBJECT, "source");
                //.addParameter(AnnotatedClick.TypeUtil.PROVIDER, "finder");

        for (BindClickField field : mFields) {
            ListenerClass listenerClass = field.getListenerClass();
            ListenerMethod method = listenerClass.method()[0];
            String name = field.getFieldName().toString();
            //匿名内部类
            //编译的时候会检查类。java库找不到Android类,会报错
//            TypeSpec onCLick = TypeSpec.anonymousClassBuilder("")
//                    .addSuperinterface(ParameterizedTypeName.get(Class.forName(listenerClass.type())))
//                    .addMethod(MethodSpec.methodBuilder(method.name())
//                            .addAnnotation(Override.class)
//                            .addModifiers(Modifier.PUBLIC)
//                            .addParameter(VIEW, "view")
//                            .returns(void.class)
//                            .addStatement("host.$L();", name)
//                            .build())
//                    .build();
//            bindViewMethod.addStatement("host.findViewById($L).setOnClickListener($L)", field.getResId(), onCLick);

            //可以这么写,但总感觉不标准
//            bindViewMethod.addStatement("host.findViewById($L).setOnClickListener(new View.OnClickListener() { @Override public void onClick($T v) { host.$L(); }})",
//                    field.getResId(), VIEW, name);

            //已经简化过了,但是感觉和标准还差那么点
//            bindViewMethod.addStatement("host.findViewById($L).$L(new $L() { @Override public void $L($T v) { host.$L(); }})",
//                    field.getResId(), listenerClass.setter(), listenerClass.type(), method.name(), VIEW, name);

            TypeSpec onCLick = TypeSpec.anonymousClassBuilder("")
                    .superclass(ClassName.bestGuess(listenerClass.type()))
                    .addMethod(MethodSpec.methodBuilder(method.name())
                            .addAnnotation(Override.class)
                            .addModifiers(Modifier.PUBLIC)
                            .addParameter(VIEW, "view")
                            .returns(void.class)
                            .addStatement("host.$L()", name)
                            .build())
                    .build();
            bindViewMethod.addStatement("host.findViewById($L).$L($L)", field.getResId(), listenerClass.setter(), onCLick);
        }

        MethodSpec.Builder unBindViewMethod = MethodSpec.methodBuilder("unBindClick")
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.get(mTypeElement.asType()), "host")
                .addAnnotation(Override.class);
        for (BindClickField field : mFields) {
            unBindViewMethod.addStatement("host.findViewById($L).setOnClickListener(null)", field.getResId());
        }

        //generaClass
        TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$$ClickBinder")
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(ParameterizedTypeName.get(AnnotatedClick.TypeUtil.BINDER, TypeName.get(mTypeElement.asType())))
                .addMethod(bindViewMethod.build())
                .addMethod(unBindViewMethod.build())
                .build();

        String packageName = mElements.getPackageOf(mTypeElement).getQualifiedName().toString();

        return JavaFile.builder(packageName, injectClass).build();
    }

4.总结

其实整个的思路是挺简单的。主要是具体实现,涉及很多不常用到的细节,处理起来比较困难

github链接:https://github.com/lxj1137800599/BindTest

你可能感兴趣的:(AnnotationProcessor实战:实现ButterKnife的setOnClickListener方法)