有了前一篇文章的理解,其实这个功能并不会太难
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