專案需求討論-擺脫EditText內容規則的枯燥判斷

青蛙要fly發表於2017-08-22

      大家好,又到了新的一期的專案需求討論。我想大家在開發APP,肯定會有很多需要填入EditText內容的介面,比如註冊介面,修改密碼介面。這些介面都會有很多個相應的EditText。同時每個EditText需要填寫的內容不同,所以就造成我們對於每個EditText進行相應的判斷。

比如下面的介面:

可能我們需要輸入“使用者名稱”、“地址”、“郵箱”、“電話”。然後下面可能就有一個“註冊”的按鈕,當我們按下“註冊”按鈕的時候。我們可能平時都是這麼做的:

  1. 獲取了四個EditText的物件
    private EditText mNameEditText;
    private EditText mAddressEditText;
    private EditText mEmailEditText;
    private EditText mPhoneEditText;
    mNameEditText = (EditText) findViewById(R.id.nameEditText);
    mAddressEditText = (EditText) findViewById(R.id.addressEditText);
    mEmailEditText = (EditText) findViewById(R.id.emailEditText);
    mPhoneEditText = (EditText) findViewById(R.id.phoneEditText);複製程式碼
  2. 獲取他們的內容,從上往下,一個個判斷他是不是為空,如果為空,我們就提示使用者漏填了某個內容:
    if(TextUtils.isEmpty(mNameEditText.getText().toString()){
        Toast.makeText(this, "使用者名稱是必填項,請輸入內容", Toast.LENGTH_SHORT).show();
        return;
    }
    ....
    ....
    ....複製程式碼
  3. 當每個都填了內容後,你可能還要相應的不同的EditText還有相應的規則,比如我們上面已經判斷了使用者名稱不為空了。然後我們的APP有規定,使用者名稱的長度不能小於5同時不能大於10,然後你又要寫:
    int nameLength = mNameEditText.getText().toString().length();
    if(nameLength < 5 || nameLength > 10){
        Toast.makeText(this, "使用者名稱長度不能小於5且不能大於10", Toast.LENGTH_SHORT).show();
        return;
    }複製程式碼
  4. 然後你還要對相應的EditText去一個個判斷郵箱規則,電話規則,如果還要“密碼”的EditText,一般同事還有一個“確認密碼”的EditText,這時候你不僅要第一個EditText符合密碼規則,然後還要判斷二個EditText的內容是不是相等。反正是麻煩至極,而且程式碼基本都是很多地方都是重複,讓閱讀也變得很差。

      所以程式設計師本著對於這些程式碼的懶惰原則。我就去尋找相關的優秀的工具,這不,本文的主角出場了:
android-saripaar

這裡我們分別對於本文主角的使用功能來進行介紹:

基本使用方法:

比如我們上面講到的使用者名稱不能為空,我們看下用saripaar是怎麼來使用

  1. 在我們定義的EditText的引用上方,新增相應功能的註解即可
    @NotEmpty
    private EditText mNameEditText;複製程式碼
  2. 建立Validator物件,並且設定EditText中的內容規則判斷後的回撥事件:

    Validator mValidator = new Validator(this);
    mValidator.setValidationListener(new Validator.ValidationListener() {
         @Override
         public void onValidationSucceeded() {
             //符合我們新增的相關規則,驗證成功
         }
    
         @Override
         public void onValidationFailed(List<ValidationError> errors) {
             //不符合我們新增的相關規則,驗證失敗
         }
    });複製程式碼
  3. 在某種條件下(比如按下注冊按鈕)呼叫驗證方法:
    mValidator.validate();複製程式碼

基本用法就是上面那樣,可能大家會說,老司機你就這樣??這個功能也太少了吧。別急,容許老司機一步步來介紹相關詳細內容。


基本的註解:

比如有一些:
@NotEmpty :非空
@Length: 長度
@Email: 郵箱
@Password:密碼(預設是6位)
@ConfirmPassword:確認密碼
@Checked:CheckBox是否被選中
@Length:長度判斷
IpAddress:ip地址判斷

等,我就不列舉了,直接看圖吧


@Order:

我們一般來說介面上會有好幾個EditText,比如name,email,address三個輸入框,我們會對三個輸入框都設定相關的規則,這時候每個app中對於這些輸入框的判斷的順序有所要求,比如先是判斷name,然後依次往下判斷,但有些可能先判斷address等。
所以@Order就是用來讓我們驗證好幾個EditText時來進行排序的。

@NotEmpty
@Order(1)
private EditText mNameEditText;

@NotEmpty
@Order(2)
private EditText mAddressEditText;

@Email
@Order(3)
private EditText mEmailEditText;複製程式碼

這時候大家可能就會問了。你這裡設定了@Order,那順序體現在哪裡呢?就在我們上面設定過的Listener中:

@Override
public void onValidationFailed(List<ValidationError> errors) {

}複製程式碼

我們可以看到,這個失敗的方法裡面的引數是List<ValidationError> errors
我們可以看下ValidationError的原始碼裡面有這麼幾個方法:

public View getView() {
    return view;
}

public List<Rule> getFailedRules() {
    return failedRules;
}

public String getCollatedErrorMessage(final Context context) {
    StringBuilder stringBuilder = new StringBuilder();
    for (Rule failedRule : failedRules) {
        String message = failedRule.getMessage(context).trim();
        if (message.length() > 0) {
            stringBuilder.append(message).append('\n');
        }
    }
    return stringBuilder.toString().trim();
}複製程式碼

我們一眼就會發現getView這個方法就是我們所需要的,沒錯,如果我們有好幾個EditText都不符合規則,在List`<ValidationError>中就會按照我們寫的@Order的順序來進行排序。

比如我們想讓EditText不符合規則的時候出現:


我們只需要:

@Override
public void onValidationFailed(List<ValidationError> errors) {
    for (ValidationError error : errors){
        ((EditText) error.getView()).setError("不符合規則");
    }
}複製程式碼

也許有人會說。我希望name錯了的時候就提示name,後面的就不管了。報第一個不符合規則的錯誤,然後有人會說errors.get(0).getView()獲取,當然這樣是可以的,但是saripaar已經幫我考慮到了:

//全部不符合規則
mValidator.setValidationMode(Validator.Mode.BURST);

//按照順序第一個不符合規則的
mValidator.setValidationMode(Validator.Mode.IMMEDIATE);複製程式碼

我們設定了後,同樣的程式碼就變成了:


(message = ""):

有小夥伴會說,你上面提示的內容的都是不符合規則,我們想要不同的EditText不符合規則後提示不同的內容,還記得我們上面ValidationError有個方法getCollatedErrorMessage(context),沒錯,我們可以給每個EditText設定不同的message,然後在驗證失敗後,顯示相應的message即可:

@NotEmpty(message = "名字不能為空")
private EditText mNameEditText;

@Override
public void onValidationFailed(List<ValidationError> errors) {
    for (ValidationError error : errors){
        ((EditText) error.getView()).setError(error.getCollatedErrorMessage(OrderedValidateActivity.this));
    }
}複製程式碼


sequence:

我們有時候對於一個EditText會有多種要求,比如不僅不能為空,而且同時要符合郵箱的標準,這時候我們對於驗證也希望有驗證順序,比如先判斷是否為空,如果為空,直接就提示錯誤了。如果不為空再判斷是不是符合郵箱的規則。

@NotEmpty(sequence = 1, message = "不能為空")
@Email(sequence = 2, message = "不符合郵箱規則")
private EditText mEmailEditText;複製程式碼

這時候你會發現這個EditText就會按照你所規定的規則順序來判斷。
但這裡注意了,上面提過我們獲取message是用

error.getCollatedErrorMessage(context);複製程式碼

因為上面我們一個EditText只新增了一個規則判斷,所以無所謂,比如我這裡新增了二個,我們再看的時候會變成怎麼樣?


沒錯,雖然判斷規則的順序的確是按照我們寫的那樣,但是,你發現了,error.getCollatedErrorMessage(context);方法獲取到的message的內容是全部不符合規則的message的集合。但我們想要的是非空的時候先提示不能為空,然後在不為空的條件下,不是郵箱格式,再提示郵箱不符合郵箱格式。

還記不記得我們已經介紹了上面ValidationError的二個方法,還有一個方法getFailedRules()沒介紹過,沒錯,我們可以用這個,從字面意思我們就可以理解,獲取到失敗的規則的集合,而且這個集合的順序就是我們設定的sequence的順序,我們這麼寫:

for (ValidationError error : errors){

    ((EditText) error.getView()).
    setError(error.getFailedRules().get(0)
    .getMessage(OrderedValidateActivity.this));

}複製程式碼

我們獲取第一個不符合的規則的Message即可。
看效果:


@Optional:

很多時候我們一些資訊是非必填的,比如還是email,我們可以為空,但是如果你填了,那麼一定就要符合郵箱的規則,這時候@Optional就起到作用了:

@Optional 
@Email 
EditText mEmailEditText;複製程式碼

你可以不填,這時候驗證是通過的,但是如果你填了內容,就一定要符合email規則。使用起來什麼方便。


validateTill 和 validateBefore

我們上面在最後起到驗證功能是呼叫了

mValidator.validate();複製程式碼

同時它還提供了:

mValidator.validateTill(view);
mValidator.validateBefore(view);複製程式碼

從字面意思我們就知道,到某個View為止的規則檢驗,及某個View前的規則檢驗。

validateTill:比如有a,b,c,d四個View,並且order的順序相對應也是1,2,3,4,
比如當前四個View都不符合規則,並且你呼叫了validateTill(c),那麼我們的List<ValidationError> errors中就包含了a,b,c。如果你呼叫了validateBefore(c),則List<ValidationError> errors就包含了a,b,也就是比傳入的View的Order小的會被包含。

同時你在使用的時候會發現他們三個方法都有另外的過載方法,分別是:

validate(boolean);
validateTill(view ,boolean);
validateBefore(view,boolean);複製程式碼

當那個Boolean值為true的時候,就是說我們可以把驗證的過程放在後臺的AsyncTask中去執行。


@AssertTrue 和 @AssertFalse

我們有些輸入框可能不是通用的規則,像郵箱啊,電話號碼什麼的,比如某個輸入框的判斷規則是"青蛙要fly好帥",當EditText的內容是這個的時候才能認為通過。這時候我們就要新增自己的規則。可以使用@AssertTrue來判斷是否符合你定義的規則。

@AssertTrue
private EditText testEt;

mValidator.registerAdapter(EditText.class, new ViewDataAdapter<EditText, Boolean>() {
    @Override
    public Boolean getData(EditText view) throws ConversionException {
        return "青蛙要fly好帥".equals(view.getText().toString());
    }

    @Override
    public <T extends Annotation> boolean containsOptionalValue(EditText view, T ruleAnnotation) {
        return false;
    }
});複製程式碼

Custom Annotation

當然我們還可以自己制定相關的註解,比如我現在自己寫一個@CoolBoy,用來判斷EditText是否符合我寫的相關內容:

CoolBoy.java

@ValidateUsing(CoolBoyRule.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CoolBoy {

    @StringRes int messageResId()   default -1;
    String message()                default "不能說青蛙要fly不帥氣";
    int sequence()                  default -1;
}複製程式碼

CoolBoyRule.java

public class CoolBoyRule extends AnnotationRule<CoolBoy, String> {

    protected CoolBoyRule(final CoolBoy coolBoy) {
        super(coolBoy);
    }

    @Override
    public boolean isValid(String data) {
        return "青蛙要fly是個帥哥".equals(data);
    }
}複製程式碼

最後一步:
在Validator.java中新增我們剛宣告好的註解:

SARIPAAR_REGISTRY.register(
    ConfirmEmail.class, ConfirmPassword.class, CreditCard.class,
    Digits.class, Domain.class, Email.class, Future.class,
    IpAddress.class, Isbn.class, Length.class, NotEmpty.class,
    Password.class, Past.class, Pattern.class, Url.class, CoolBoy.class);

//在最後新增了我們的CoolBoy.class複製程式碼

然後在我們的程式碼中使用:

 @CoolBoy
 private EditText mNameEditText;複製程式碼


補充

我們在validator.java中還可以看到其他的申明等:

static {
    // CheckBoxBooleanAdapter
    SARIPAAR_REGISTRY.register(CheckBox.class, Boolean.class,
        new CheckBoxBooleanAdapter(),
        AssertFalse.class, AssertTrue.class, Checked.class);

    // RadioGroupBooleanAdapter
    SARIPAAR_REGISTRY.register(RadioGroup.class, Boolean.class,
        new RadioGroupBooleanAdapter(),
        Checked.class);

    // RadioButtonBooleanAdapter
    SARIPAAR_REGISTRY.register(RadioButton.class, Boolean.class,
        new RadioButtonBooleanAdapter(),
        AssertFalse.class, AssertTrue.class, Checked.class);

    // SpinnerIndexAdapter
    SARIPAAR_REGISTRY.register(Spinner.class, Integer.class,
        new SpinnerIndexAdapter(),
        Select.class);

    // TextViewDoubleAdapter
    SARIPAAR_REGISTRY.register(DecimalMax.class, DecimalMin.class);

    // TextViewIntegerAdapter
    SARIPAAR_REGISTRY.register(Max.class, Min.class);

    // TextViewStringAdapter
    SARIPAAR_REGISTRY.register(
        ConfirmEmail.class, ConfirmPassword.class, CreditCard.class,
        Digits.class, Domain.class, Email.class, Future.class,
        IpAddress.class, Isbn.class, Length.class, NotEmpty.class,
        Password.class, Past.class, Pattern.class, Url.class, CoolBoy.class);
}複製程式碼

哈哈,更多的demo大家也可以看:
android-saripaar
裡面有相關的testdemo的使用。歡迎大家吐槽。哈哈,還是老話,哪裡錯了,希望大家能指出。

相關文章