在前面的一步一步,实现自己的ButterKnife(一)中,我们已经知道了如何实现在Activity中用注解@BindView来实现findViewById的功能。
文章发布后,有朋友问能不能将setContentView()这个方法也给用注解来Bind,这个目前ButterKnife中并没有做到。如果实现了的话,那么只需要在BaseActivity中的onCreate方法中加入ButterKnife.bind(this);那么所以BaseActivity的子类就可以连onCreate方法都不用写了,减轻了多少代码量啊!不懂得偷懒的程序员不是好程序员(^__^) 。
那么,我们来实现,用注解来生成setContentView代码吧。
本文实现的功能
使用@BindLayout来注解一个Activity,为Activity自动在onCreate方法中添加setContentView语句,进而免去onCreate方法的手工代码实现。项目链接不变:点击前往Github:ButterFork.
改进细节
首先当然是在annotation module中增加注解:
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface BindLayout {
int value();
}
我们是用来给类进行注解的,所以这里的Target是ElementType.TYPE了,而不是FIELD。
然后,在sample module里把注解给用上:
@BindLayout(R.layout.activity_main)
public class MainActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ButterFork.bind(this);
}
}
看过文章(一)的朋友应该记得,ButterFork.bind()实质是通过反射去调用了自动生成的BindMainActivity.bind()方法,这里边有@BindView自动生成的findViewById语句,这次,我们把setContentView语句也写进去就可以了。
要自动生成语句,当然要改动的就是compiler module里的BindProcessor了,新增的代码只5行,见注释。
private void generateJavaClass() {
for (TypeElement enclosedElem : mBindViewElems.keySet()) {
//generate bind method
MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(ClassName.get(enclosedElem.asType()),"activity")
.returns(TypeName.VOID);
//新增代码 start
BindLayout bindLayoutAnno = enclosedElem.getAnnotation(BindLayout.class);
if (bindLayoutAnno != null){
methodSpecBuilder.addStatement(String.format(Locale.US,"activity.setContentView(%d)",bindLayoutAnno.value()));
}
//新增代码 end
for (Element bindElem : mBindViewElems.get(enclosedElem)) {
methodSpecBuilder.addStatement(String.format(Locale.US,"activity.%s = (%s)activity.findViewById(%d)",bindElem.getSimpleName(),bindElem.asType(),bindElem.getAnnotation(BindView.class).value()));
}
TypeSpec typeSpec = TypeSpec.classBuilder("Bind"+enclosedElem.getSimpleName())
.superclass(TypeName.get(enclosedElem.asType()))
.addModifiers(Modifier.FINAL,Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.build();
JavaFile file = JavaFile.builder(getPackageName(enclosedElem),typeSpec).build();
try {
file.writeTo(processingEnv.getFiler());
} catch (IOException e) {
// e.printStackTrace();
}
}
很简单,遍历到当前类,如果有被BindLayout进行注解,就把该注解的value,也就是layoutId拿来生成setContentView语句,最后自动生成的类如下:
public final class BindMainActivity extends MainActivity {
public static void bind(MainActivity activity) {
activity.setContentView(2130968604); //这句是本次改动中新生成的
activity.mBtn = (android.widget.Button)activity.findViewById(2131427422);
activity.mTextView = (android.widget.TextView)activity.findViewById(2131427423);
}
}
这样,就实现了利用@BindLayout来生成setContentView语句的想法。
Tips
Activity的onCreate方法中,现在只有ButterFork.bind(this);这一句代码了,我们可以建一个BaseActivity
public abstract class BaseActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ButterFork.bind(this);
}
}
这样一来,子类Activity连onCreate方法都不用写了,通过BindLayout来指定layoutId,通过BindView来指定viewId,偷懒成功!
@BindLayout(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@BindView(R.id.btn)
protected Button mBtn;
@BindView(R.id.text)
protected TextView mTextView;
}