一个小需求引发的思考

4,138 阅读5分钟

在平时开发过程中难免为了赶进度或者在比较短的时间里写一个功能,我们一般都简单粗暴的以解决问题为目的,我想对于这样的代码,而后再细细思考才是,没准会有新的发现,今天我就遇到了这么一个小需求。

需求如下:

如下图,有两个输入框,一个按钮,需求是当两个EditText都输入内容的时候,按钮才能亮起。

image.png
image.png

当时快下班了,又要马上发包了,时间紧,又必须解决这个问题,所以干脆用最简单的做法限制实现才是王道,于是匆匆写了代码是这样的:

et1.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                //检查两个editText 的文本是否为空,都不为空时,按钮亮起
                if(Util.checkEmpty(et1.getText().toString().trim()) 
                        && Util.checkEmpty(et2.getText().toString().trim())){
                    btnActive.setEnabled(true);
                }else{
                    btnActive.setEnabled(false);
                }
            }
        });

et2.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                //检查两个editText 的文本是否为空,都不为空时,按钮亮起
                if(Util.checkEmpty(et1.getText().toString().trim()) 
                        && Util.checkEmpty(et2.getText().toString().trim())){
                    btnActive.setEnabled(true);
                }else{
                    btnActive.setEnabled(false);
                }
            }
        });

一个ctrl + c 和ctrl + v,实现了,当时写完内心是崩溃的其实,感觉哪里不舒服,要是有5个呢,会不会感觉有点长,当时也就这么一想,当天就先打完包,发出去了。

第二天

来到公司,看到昨天写的代码特别不爽,还是得改改啊,当时想既然每个EditText都要添加一个addTextChanged的方法,那就让当前的Activity实现好了,不用每个都实现,于是修改后的代码如下:

class MainActivity extends Activity implements TextWatcher{
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_mian);

       et1.addTextChangedListener(this);
       et2.addTextChangedListener(this);
  }

 @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {

    }

    @Override
    public void afterTextChanged(Editable s) {

        btnActive.setEnabled(checkState());
    }

    public boolean checkState() {
        return Util.checkEmpty(et1.getText().toString().trim()) &&       
          Util.checkEmpty(et2getText().toString().trim());
    }
}

这样好像看起来整洁多了哈,至少比之前的好一点了,然后感觉这个很像观察者模式,为什么呢,你看这个Button 很像观察者,EditText很像被观察者,Button 观察的是 EditText 文本的变化,只要它一变化其实就应该通知 Button,Button 内部循环遍历所有被观察者,是否满足要求,基于这种想法,我又创作了我的第三版本,自定义一个Button吧,很简单,只需要内部一个observer 方法,告诉button你需要观察的对象,只需要一行代码,于是最后的就是这样的:

btnActive.observer(et1,et2);

自定义Button 代码如下:

public class ButtonObserver extends android.support.v7.widget.AppCompatButton implements TextWatcher {

    ArrayList<EditText> list = new ArrayList<>();

    public ButtonObserver(Context context) {
        this(context,null);
    }

    public ButtonObserver(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ButtonObserver(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * watch
     * @param ets
     */
    public void observer(EditText... ets){
        //遍历所有的et
        for(EditText et : ets){
            et.addTextChangedListener(this);
            list.add(et);
        }
    }

    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void afterTextChanged(Editable editable) {

        setEnabled(checkEmpty());
    }

    public boolean checkEmpty(){
        boolean isFlag = true;

        for(EditText et : list){
            if(TextUtils.isEmpty(et.getText().toString().trim())){
                isFlag = false;
                break;
            }
        }
        return isFlag;
    }
}

好吧,至此,只需要在那个activity里写一行代码就可以完成了,我想应该就这样了吧,为了方便下次自己用或者别人用的时候简单点,能用一行代码解决的事情千万不要写两行代码,能封装的就封装,代码看起来也很整洁,至此,我觉着应该可以了吧。

要是可以像ButterKnife那样就好了,在Button 观察者上面加一个注解,把需要观察的Edittext 的id 传进去,在oncreate 方法中注册一下,就实现了该多好啊,想要实现这样的效果:

    @MyTextWatcher({R.id.et1,R.id.et2})
    Button btnActive;

    AnnotateUtils.register(this);

看起来,还挺好,但是好像多年未学的注解忘记啦,于是开始网上找资料先学习有关注解的知识,期间又涉及到反射相关的知识,好嘛,那就一块吧。

经过了一下午的学习,好吧,大概知道怎么用了,关于注解方面的知识,我会自己单独再写一篇文章,这里就不多说了,首先先自定义一个注解:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTextWatcher {
    int[] value();
}

Target 标识我这个注解应用的目标范围是啥,ElementType.FIELD就是应用在属性变量上面;Retention是代码编译后保留运行在什么时期,这里是运行时

逻辑实现当然得需要另外一个类了,回想人家这个ButterKnife 使用的时候,不是得调用一个 ButterKnife,bind(this),类似的还有:EventBus.getDefault().register(this),我们也可以写一个工具类,AnnotateUtils.register(this)嘛,具体代码如下:

public class AnnotateUtils {

    public static void register(final Activity activity) {

        final ArrayList<EditText> list = new ArrayList<>();

        //获取activity的Class
        Class<? extends Activity> object = activity.getClass();
        //通过Class获取activity的所有字段
        Field[] fields = object.getDeclaredFields();
        //遍历所有字段
        for (final Field field : fields) {
            //获取标志有注解的字段
            MyTextWatcher myTextWatcher = field.getAnnotation(MyTextWatcher.class);

            if (myTextWatcher != null) {
                //该字段使用了注解
                int[] viewId = myTextWatcher.value();//获取字段注解的参数,这就是我们传进去控件Id
                for (int id : viewId) {
                    EditText et = activity.findViewById(id);

                    list.add(et);
                    et.addTextChangedListener(new TextWatcher() {
                        @Override
                        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

                        }

                        @Override
                        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

                        }

                        @Override
                        public void afterTextChanged(Editable editable) {

                            try {

                                //field 就是当前添加了注解的字段
                                field.setAccessible(true);
                                Button btn = (Button) field.get(activity);
                                btn.setEnabled(checkEmpty(list));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    });
                }
            }

        }
    }

}

这样我们在代码里就可以直接在Button上加上一个注解了,并且解决了我们一开始的问题,不过我自己感觉这样写总感觉还是有点不妥,为什么说不妥呢,因为这个像ButterKnife人家那个注解例如 @BIndVIew(R.id.tv1),都是在编译时期自动生成代码,不会影响程序运行之后影响性能,而我这个是在运行时,是有一点消耗性能的,如果可以动态生成那就感觉完美了,目前没想到好的解决办法,有知道的小伙伴,可以私信或评论哦。

总结:虽然需求不大,但是一定在写完代码后多思考,多动脑,多动手,没准会有新的发现,学习到新的东西,明天就是周末了,祝大家周末happy。