一個小需求引發的思考

倩倩_糖葫蘆發表於2017-11-24

在平時開發過程中難免為了趕進度或者在比較短的時間裡寫一個功能,我們一般都簡單粗暴的以解決問題為目的,我想對於這樣的程式碼,而後再細細思考才是,沒準會有新的發現,今天我就遇到了這麼一個小需求。

需求如下:

如下圖,有兩個輸入框,一個按鈕,需求是當兩個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。

相關文章