一步一步,實現自己的ButterKnife(二)

ZxLee發表於2019-03-04

在前面的一步一步,實現自己的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;
}複製程式碼

相關文章