一个完全摆脱findViewById的自动绑定库

代码地址如下:
http://www.demodashi.com/demo/13504.html

问题

先来看一个正常的写法:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="TEXT_VIEW"
        android:textSize="22sp"/>

    <Button
        android:id="@+id/btn_test"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="BUTTON"/>
</LinearLayout>
public class StartActivity extends AppCompatActivity implements View.OnClickListener {
    private TextView tv_test;
    private Button btn_test;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);

        // init view
        tv_test = findViewById(R.id.tv_test);
        btn_test = findViewById(R.id.btn_test);

        // init listener
        btn_test.setOnClickListener(this);

        tv_test.setText("Text changed!");
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_test:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

以上的那些变量的定义、findViewById、setOnClickListener等等,写起来重复繁琐,我们下面就是要解决这些问题。

流行的解决方法

一、DataBinding
  • 没有解决setOnClickListener的问题,解决了也是很繁琐,需要设置各种Handler
public class StartActivity extends AppCompatActivity implements View.OnClickListener {
    private ActivityStartBinding mBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_start);

        mBinding.tvTest.setText("Text changed!");
        mBinding.btnTest.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_test:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}
二、ButterKnife
  • 还是要定义各种变量,当界面比较复杂的时候,代码看起来就没那么好看了
  • 在子module中使用会非常繁琐
public class StartActivity extends AppCompatActivity {

    @BindView(R.id.tv_test) TextView tv_test;
    @BindView(R.id.btn_test) Button btn_test;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);

        ButterKnife.bind(this);

        tv_test.setText("Text changed!");
    }

    @OnClick(R.id.btn_test)
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_test:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}
三、直接使用 android:onClick="onClick"
  • Fragment中设置此属性无效
  • 支持库中的组件如AppCompatButton等等,设置此属性无效

还有很多其它解决方法,这里就不一一列举了。

我的解决方法

  • 凡是设置了id的view,直接使用即可,变量名为id名
  • 凡是设置了android:onClick="onClick",在Activity中实现onClick方法即可
@BindActivity(value = R.layout.activity_main, layoutFilename = "activity_main", extendsClass = "android.support.v7.app.AppCompatActivity")
public class MainActivity extends MainActivity_ {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        tv_test.setText("Text changed!");
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_test:
                Toast.makeText(this, "click", Toast.LENGTH_SHORT).show();
                break;
        }
    }
}

实现过程

原理是配置一些注解,在编译期间生成相应文件,那些繁琐的代码就放在相应文件中了。

  • 定义注解

传入三个参数:布局文件ID、布局文件名、需要继承的类

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface BindActivity {
    /**
     * layoutResID
     */
    int value();

    String layoutFilename();

    String extendsClass();
}
  • 继承AbstractProcessor类,在编译期间会回调到此类的process方法,在此方法中获取注解,生成相应文件
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    Project project = Project.init();

    Set<? extends Element> bindActivityElements = roundEnvironment.getElementsAnnotatedWith(BindActivity.class);
    for (Element element : bindActivityElements) {
        BindActivityHandler handler = new BindActivityHandler(mFiler, mElementUtils);
        handler.bind(project, element);
    }

    Set<? extends Element> bindFragmentElements = roundEnvironment.getElementsAnnotatedWith(BindFragment.class);
    for (Element element : bindFragmentElements) {
        BindFragmentHandler handler = new BindFragmentHandler(mFiler, mElementUtils);
        handler.bind(project, element);
    }

    return false;
}
  • 读取settings.gradle文件,获取所有module,在遍历module下面的所有布局文件

  • 解释布局文件,获取所有设置了id和设置了onclick的view

private void parserElement(Element element) {
    IdView view = null;
    for (Iterator<Attribute> it = element.attributeIterator(); it.hasNext(); ) {
        Attribute attribute = it.next();
        switch (attribute.getQualifiedName()) {
            case "android:id":
                view = new IdView();
                view.setType(element.getName());
                view.name = attribute.getValue().substring(5);
                idViews.add(view);
                break;
            case "android:onClick":
                // must set android:onClick="onClick"
                if (view != null && "onClick".equalsIgnoreCase(attribute.getValue())) {
                    view.hasOnClick = true;
                }
                break;
        }
    }
    if (view != null) System.out.println(view.toString());
}
  • 根据注解找到对应的布局文件,生成对应的类文件

后会生成一个文件名类似MainActivity_的文件,和MainActivity在同一个目录,大致为build/generated/source/apt/debug/包名

@Override
public void bind(Project project, Element element) {
    String _package = getPackageName(element);
    String _class = getClassName(element);

    BindActivity bindActivity = element.getAnnotation(BindActivity.class);
    int layoutResID = bindActivity.value();
    String layoutFilename = bindActivity.layoutFilename();
    String extendsClass = bindActivity.extendsClass();

    Layout layout = project.getLayout(layoutFilename);
    if (layout != null) {
        try {
            JavaFileObject jfo = mFiler.createSourceFile(_package + "." + _class, new Element[]{});
            Writer writer = jfo.openWriter();
            StringBuilderHelper sb = new StringBuilderHelper();
            sb.line("package " + _package + ";");
            sb.line();
            sb.line("import android.os.Bundle;");
            sb.line("import android.support.annotation.Nullable;");
            sb.line("import android.view.View;");
            sb.line();
            sb.line("// Generated code, dot not edit!!!");
            sb.line();
            if (layout.hasOnClick()) {
                sb.line("public abstract class " + _class + " extends " + extendsClass + " implements View.OnClickListener {");
            } else {
                sb.line("public abstract class " + _class + " extends " + extendsClass + " {");
            }
            sb.line();
            sb.line____("protected final int layoutResID = " + layoutResID + ";");
            sb.line();
            sb.line____("protected View rootView;");
            for (IdView idView : layout.idViews) {
                sb.line____("protected " + idView.getType() + " " + idView.name + ";");
            }
            sb.line();
            sb.line____("@Override");
            sb.line____("protected void onCreate(@Nullable Bundle savedInstanceState) {");
            sb.line________("super.onCreate(savedInstanceState);");
            sb.line________("setContentView(layoutResID);");
            sb.line();
            for (IdView idView : layout.idViews) {
                sb.line________(idView.name + " = findViewById(R.id." + idView.name + ");");
            }
            sb.line();
            initOnClick(layout, sb);
            sb.line____("}");
            sb.line("}");
            writer.write(sb.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用方法

比如类为MainActivity,继承AppCompatActivity,布局文件为R.layout.activity_main

  • 设置类注解
@BindActivity(value = R.layout.activity_main, 
        layoutFilename = "activity_main", 
        extendsClass = "android.support.v7.app.AppCompatActivity")
  • 布局文件设置id属性和onClick属性
<Button
    android:id="@+id/btn_test"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="onClick"
    android:text="BUTTON"/>
  • MakeProject
  • 修改MainActivity继承MainActivity_即可

文件结构图

演示效果图

运行代码可能出现的问题

  • compileSdkVersion 27,可以改成你电脑中存在的SDK版本。

  • 这里用的是 gradle-4.4-all.zip,如果你用的是其它版本,那么可能会下载超级慢,建议改成你电脑中存在的gradle版本,改文件PermissionHelper/gradle/wrapper/gradle-wrapper.properties即可。

  • 其它问题可以直接联系我。一个完全摆脱findViewById的自动绑定库

代码地址如下:
http://www.demodashi.com/demo/13504.html

注:本文著作权归作者,由demo大师代发,拒绝转载,转载需要作者授权

猜你喜欢

转载自www.cnblogs.com/demodashi/p/9443472.html