• 一个完全摆脱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大师代发,拒绝转载,转载需要作者授权

  • 相关阅读:
    Android git提交代码所需忽略的文件
    python 代码命名规范
    appium-设计思路
    appium-循环执行一条用例,失败时停止执行
    BeautifulReport 遇到的问题 template
    接口自动化
    python-装饰器
    linux-vi编辑器创建和编辑正文文件
    linux-文件的压缩与解压缩
    python学习第一天
  • 原文地址:https://www.cnblogs.com/demodashi/p/9443472.html
Copyright © 2020-2023  润新知