前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AnnotationProcessor实战:实现ButterKnife的setOnClickListener方法

AnnotationProcessor实战:实现ButterKnife的setOnClickListener方法

作者头像
提莫队长
发布2020-06-02 15:27:27
6690
发布2020-06-02 15:27:27
举报
文章被收录于专栏:刘晓杰

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

1.如何定义注解BindClick

代码语言:javascript
复制
    @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

代码语言:javascript
复制
@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接口

代码语言:javascript
复制
public interface ClickBinder<T> {
    void bindClick(T host, Object object);

    void unBindClick(T host);
}

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

代码语言:javascript
复制
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的成员变量

代码语言:javascript
复制
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函数

代码语言:javascript
复制
    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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.如何定义注解BindClick
  • 2.如何获取标注了BindClick的成员信息
  • 3.如何生成setOnClickListener方法对应的代码呢?
  • 4.总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档