一步一步,实现自己的ButterKnife(二)

982 阅读2分钟

在前面的一步一步,实现自己的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;
}