Android Form元件

玩毛線的包子發表於2018-07-24

場景

Android APP裡面時常有於登入、註冊、資訊填寫提交等頁面,業務邏輯差不多都是使用者輸入或者選擇某些資訊,然後點選提交,客戶端這邊依次判斷使用者名稱是否為空,長度是否在某個範圍內等等,如果某一個不符合要求,就提示相應資訊,如果都符合,就組合成key=value形式發起請求。

引申

上述場景類似於HTML裡面的form標籤

<form action="form_action.asp" method="get">
  <p>First name: <input type="text" name="fname" /></p>
  <p>Last name: <input type="text" name="lname" /></p>
  <input type="submit" value="Submit" />
</form>
複製程式碼

在Android裡面,登入、註冊這些頁面是否也像一個個form呢,我們能否也用一個配置檔案配置請求的URL,請求方式,然後配置相關的控制元件,最後在點選提交按鈕時自動組裝?

再引申一步,登入頁面裡面,我們需要判斷使用者名稱為空、長度、匹配規則等,密碼為空、長度、匹配規則等,然後分別給提示。在資訊填寫頁面就更多了,有可能需要使用者輸入好幾十條的資訊分別判斷,這樣就導致了程式碼冗餘和重複。能否在配置裡面就配置好相應的檢查規則,在點選提交的時候自動依次檢查?

答案是可以的~下面就來看看例子

示例佈局

Android Form元件

<?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"
    android:padding="20dp">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_vertical"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/chose_sex"
            />
        <RadioGroup
            android:id="@+id/rg_sex"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            >
            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/sex_male"
                />
            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/sex_female"
                />
        </RadioGroup>
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_vertical"
        android:layout_marginTop="10dp"
        >
        <ImageView
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:src="@mipmap/user"
            />
        <EditText
            android:id="@+id/et_user"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/et_name_hint"
            android:layout_marginLeft="10dp"
            android:inputType="number"
            />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_vertical"
        android:layout_marginTop="10dp"
        >
        <ImageView
            android:layout_width="25dp"
            android:layout_height="25dp"
            android:src="@mipmap/password"
            />
        <EditText
            android:id="@+id/et_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:hint="@string/et_password_hint"
            android:layout_marginLeft="10dp"
            android:inputType="textPassword"
            />
    </LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center_vertical"
        android:layout_marginTop="10dp"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/protocol_num"
            />
        <com.kongge.formlikedemo.view.DIYView
            android:id="@+id/diy_view"
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:layout_marginLeft="10dp"
            />
    </LinearLayout>
    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/login_btn_bg"
        android:text="@string/btn_login"
        android:layout_marginTop="10dp"
        />
</LinearLayout>
複製程式碼

為了簡單快捷,都是用的LinearLayout,這樣影響效能,這裡當一個反面教材吧。。。

常規寫法

  1. 先findByID,獲取全部的控制元件。
  2. 按鈕設定監聽。
  3. 按鈕點選之後依次判斷:性別是否選擇?沒有就提示;賬號是否為空?長度是否是11位?沒有分別彈提示;密碼是否為空?長度是否大於6?長度是否小於16?沒有分別彈提示;協議編號是否為空?是否都是字母?沒有彈提示。
  4. 如果還有其他資訊,依次增加同樣的邏輯。

封裝之後

json配置
{
  "action":"https://www.xxx.com",
  "method":"post",
  "item":[
    {
      "id":"rg_sex",
      "rules":{
        "notNull":[true, "性別不能為空"]
      },
      "paramName":"sex"
    },
    {
      "id":"et_user",
      "rules":{
        "notNull":[true, "賬號不能為空"],
        "length":[11, "賬號長度必須是11位"]
      },
      "paramName":"userName"
    },
    {
      "id":"et_password",
      "rules":{
        "notNull":[true, "密碼不能為空"],
        "minLength":[6, "密碼最短為6位"],
        "maxLength":[16, "密碼最長為16位"]
      },
      "paramName":"userPwd"
    },
    {
      "id":"diy_view",
      "rules":{
        "notNull":[true, "協議編號不能為空"],
        "textMatch":["[a-zA-Z]+", "協議編號需要全部為字母"]
      },
      "paramName":"protocol"
    },
    {
      "id":"btn_login",
      "type":"submit"
    }
  ]
}
複製程式碼

action:提交的URL

method:提交的方式

item:Form裡面的控制元件,比如選擇按鈕、文字框和提交按鈕等。

id:控制元件定義的id,使用反射獲取控制元件。

rules:檢測規則,比如不為空、長度檢查等。點選提交時會按照裡面配置的規則依次檢查。

paramName:點選提交時,對應的key。value就是控制元件裡面的內容。

type代表控制元件型別,無此欄位時,則預設需要獲取該控制元件內容上傳,如果為“submit”,則表示是個提交按鈕,當點選該按鈕時,會逐個檢查其他欄位是否匹配規則,如果都通過了,則會組裝全部引數返回。

程式程式碼
        // 自定義View,裡面繪製了文字
        DIYView diyView = findViewById(R.id.diy_view);
        diyView.setText("abxcSDDFsdcAd");

        // 自定義規則,名稱是“textmatch”,類名是RuleTextMatch.java
        Form.addRule("textMatch", RuleTextMatch.class);
        // 自定義檢查的View,用於檢查DIYView等其他View的輸入,可通過instanceof判斷型別之後獲取相應內容
        Form.setCheckView(DIYCheckView.class);

        String jsonStr = FileUtil.parseAssetsFile(this, "config/testConfig01.json");
        Form form = new Form();
        form.parseView(view, jsonStr);
        form.setFormCommitListener(new OnFormCommitListener() {
            @Override
            public void onFormCommitChecked(Form form, String method, String url, Map<String, String> paramMap) {
                Toast.makeText(MainActivity.this, "method=" + method + "\nurl=" + url + "\n" + "paramMap=" + paramMap.toString(), Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFormCommitError(Form form, ICheckItem checkItem, IRule errorRule) {
                Toast.makeText(MainActivity.this, errorRule.getErrorMsg(), Toast.LENGTH_SHORT).show();
            }
        });
複製程式碼
說明

由於json配置檔案裡面寫明瞭id,所以庫裡面就自動findViewByID了,外部呼叫省去了該過程。rules寫明瞭判斷規則,第一個引數是條件,第二個引數預設是未符合的情況下的提示,當然,這個在實現了IRule介面之後可以自己定義引數的順序和含義。rules裡面的順序決定了程式判斷的順序。

擴充套件

擴充套件規則

例如擴充套件一個正則匹配的規則,名稱是“textMatch”。

  1. 新建一個類RuleTextMatch繼承AbsRule,或者實現IRule介面。
public class RuleTextMatch extends AbsRule {

    private String matchFormatStr = null;
    @Override
    public void setParam(List<Object> paramList) {
        super.setParam(paramList);
        if (paramList != null && paramList.size() > 0) {
            Object item = paramList.get(0);
            if (item instanceof String) {
                matchFormatStr = (String) item;
            }
        }
    }

    @Override
    public boolean check(ICheckItem checkItem) {
        if (TextUtils.isEmpty(matchFormatStr)) {
            return true;
        }
        String content = checkItem.getCheckContent();
        if (content == null) {
            return false;
        }
        Pattern pattern = Pattern.compile(matchFormatStr);
        Matcher matcher = pattern.matcher(content);
        return matcher.matches();
    }
}
複製程式碼
  1. 註冊該規則
//在程式啟動的時候就可以註冊了,使用Form的靜態方法addRule
Form.addRule("textMatch", RuleTextMatch.class);
複製程式碼
  1. json配置
    {
      "id":"diy_view",
      "rules":{
        "notNull":[true, "協議編號不能為空"],
        "textMatch":["[a-zA-Z]+", "協議編號需要全部為字母"]
      },
      "paramName":"protocol"
    }
複製程式碼
擴充套件View

目前階段只支援RadioGroup和TextView(EditText, Button等繼承了TextView),如果想擴充套件其他的View,只需要建立一個類繼承CheckView或者實現ICheckItem介面即可。 例如我們自定義了一個DIYView。

  1. DIYView
public class DIYView extends View {
    // ...省略其他方法
    private String text;
    public String getText() {
        return text;
    }
    // ...省略其他方法
}
複製程式碼
  1. 需要擴充套件一個類DIYCheckView,來解析DIYView和其他擴充套件的View。整個外部擴充套件解析類只需要這一個就可以了。
public class DIYCheckView extends CheckView {

    @Override
    public String getCheckContent() {
        String content = super.getCheckContent();
        if (content != null) {
            return content;
        }
        // 通過instanceof判斷各個型別View的獲取文字的方法。
        if (view instanceof DIYView) {
            return ((DIYView) view).getText();
        }
        return content;
    }
}
複製程式碼
  1. 使用
// 在程式啟動時,呼叫Form的靜態方法setCheckView就可以了
Form.setCheckView(DIYCheckView.class);
複製程式碼

小結

  1. 文中的輸入資訊的就三四個,可能覺得自己寫也挺快。但是如果需要使用者填寫詳細資訊好幾十條的時候,每個都要判斷一些亂七八糟的規則,這個就輕鬆多了。
  2. 由於載入的是配置檔案,也就是說該檔案是可以隨時更新的,當某一個規則發生變化的時候可以及時更新,例如本地判斷了使用者名稱11位,後來需求更改不限制位數了,只需要更新配置檔案即可。
  3. 由於是一個aar庫,不用每次去編譯,擴充套件的時候也不需要修改庫裡面的檔案。

現階段處於起始階段,支援的規則和判斷的View比較少,後續會增加,連結如下,希望大家不膩賜教,謝謝!

git連結:FormLike

相關文章