用 RecyclerView 實現 Form 表單 靈活可複用 給你一個新思路 - MultiItem 進階

free46000發表於2017-04-17

前言

本文是MultiItem系列的進階文章,講解如何利用RecyclerView實現Form表單,在日常開發中多數人還是使用普通佈局方式實現,這種方案比較直觀也很簡單,但是如果表單業務較多,並且易變,很多弊端就會顯現,不過這正是使用RecyclerView實現的優勢所在,可以自定義一套通用的輸入型別的ItemInput元件,既靈活又可複用。MultiItem特點:

  • 直接使用業務中的實體類為RecyclerView Adapter設定資料來源,不需要做任何封裝
  • RecyclerView Adapter零編碼,解放了複雜的Adapter
  • 支援DataBinding,讓你清爽的編寫列表程式碼
  • 支援Form表單錄入,懶載入易複用,支援DataBinding、隱藏域、輸入內容驗證及是否變化

原始碼地址

Github地址:github.com/free46000/M…,請大家多多關注。

系列文章

效果截圖

用 RecyclerView 實現 Form 表單 靈活可複用 給你一個新思路 - MultiItem 進階
Form表單效果

用 RecyclerView 實現 Form 表單 靈活可複用 給你一個新思路 - MultiItem 進階
Form表單提交

用法

使用方法

首先初始化InputItemAdapter,然後新增實現ItemInput介面的資料來源,相關程式碼:
注:類庫中已提供了一些實現介面的基類如:BaseItemInput DataBindItemInput,使用時直接繼承基類就可以,

protected void initViews() {
    //初始化adapter
    adapter = new InputItemAdapter();
    List<Object> list = new ArrayList<>();

    //姓名和性別錄入Item,一個錄入item對應多個提交的值{"name":"","sex":""}
    list.add(new ItemNameAndSex());

    //普通的EditText錄入Item
    list.add(new ItemEdit("height").setName("身高:"));
    list.add(new ItemEdit("weight").setName("體重:"));
    list.add(new ItemEdit("age").setName("年齡:"));
    list.add(new ItemEdit("default").setName("國家:").setDefValue("中國"));

    //利用DataBinding的錄入Item
    list.add(new ItemInfoDataBind("info").setName("介紹:"));

    //新增user id對應的隱藏域的Item(使用者不可見)
    adapter.addHiddenItem("id", "隱藏域中攜帶id");
    adapter.setDataItems(list);

    recyclerView.setAdapter(adapter);
}複製程式碼

接下來展示提交表單的相關程式碼,提交時可以自動組裝資料,另外還提供了一些有用的api,詳見程式碼註釋:

public void submit() {
    //通過adapter.isValueChange()判斷表單內容是否改變
    //通過adapter.isValueValid()判斷表單內容是否有效
    //通過adapter.getInputJson()直接獲取表單錄入Json,還有獲取錄入Map的方法
    String tipTxt = "表單內容" + (adapter.isValueChange() ? " 已經 " : " 沒有 ") +
            "被使用者改變!\n表單  " + (adapter.isValueValid() ? " 已經 " : " 沒有 ") +
            "通過驗證!\n自動組裝的表單內容為:\n";

    //表單內容json字串,也可以通過Gson或FastJson等對字串反序列化成實體物件
    String valueTxt = adapter.getInputJson().toString(4);
    new AlertDialog.Builder(this).setTitle("提交").setMessage(tipTxt + valueTxt)
            .setPositiveButton(R.string.confirm, null).show();
}複製程式碼

ItemInput普通錄入ItemEdit

我們先來看看普通的錄入ItemEdit的編寫方式,它繼承了BaseItemInput基類,下面貼出一些關鍵的需要覆寫的方法,作用詳見註釋:

public class ItemEdit extends BaseItemInput<ItemEdit> {
    /**
     * @param key 錄入對應key
     */
    public ItemEdit(String key) {
        super(key);
    }

    @Override
    public String getValue() {
        //返回錄入的值,和{@link #getKey()}一起組裝為Map  如果為null則不組裝
        return editText == null ? defValue : editText.getText().toString();
    }

    @Override
    public boolean isValueValid() {
        //錄入的值不為空則有效;其它無效
        return !TextUtils.isEmpty(getValue());
    }

    @Override
    protected void initInputView(BaseViewHolder holder) {
        //初始化Input檢視,由於Input檢視不可以複用,所以直接在初始化檢視時設定好相關內容即可
        TextView nameText = getView(holder.itemView, R.id.text);
        nameText.setText(name);

        editText = getView(holder.itemView, R.id.editText);
        editText.setHint(hint);
        editText.setText(defValue);
    }

    ...
}複製程式碼

ItemInput一對多錄入ItemNameAndSex

上面我們已經看了普通錄入的實現,一對多錄入的方式需要在上面的基礎上,增加一些定製化的實現,所以和普通錄入重複的程式碼就不貼出來了,只貼出一些關鍵的需要覆寫的方法,作用詳見註釋:

public class ItemNameAndSex extends BaseItemInput<ItemNameAndSex> {
    //本例中需要返回兩組key-value所以去覆寫getValueMap()
     @Override
    public Object getValue() {
        //在本方法中返回兩個值的組合,作用是為判斷表單的值是否被改變提供依據
        if (nameEdit == null) {
            return null;
        }
        return nameEdit.getText().toString() + sexRadio.getCheckedRadioButtonId();
    }

    @Override
    public boolean isValueValid() {
        //如果名字輸入框錄入的值不為空則有效;其它無效
        return nameEdit != null && !TextUtils.isEmpty(nameEdit.getText().toString());
    }

    @Override
    public Map<String, Object> getValueMap() {
        if (nameEdit == null) {
            return null;
        }

        //此處自己組裝Map{name:name,sex:sex}並返回,這樣可以達到一個Item返回兩組值的效果
        Map<String, Object> valueMap = new HashMap<>(2);
        valueMap.put("name", nameEdit.getText().toString());
        int sexStrResId = sexRadio.getCheckedRadioButtonId() == R.id.man ? R.string.man : R.string.woman;
        valueMap.put("sex", nameEdit.getContext().getString(sexStrResId));

        return valueMap;
    }

    ...
}複製程式碼

ItemInput 資料繫結錄入ItemInfoDataBind

接下來我們看看資料繫結方式,貼出關鍵程式碼:

public class ItemInfoDataBind extends DataBindItemInput<ItemInfoDataBind> {

     @Override
    protected void initInputView(ViewDataBinding dataBinding) {
        //把自身例項物件通過ViewDataBinding繫結到檢視中
        dataBinding.setVariable(BR.itemData, this);
    }    
    ...
}複製程式碼

通過以上程式碼我們不難發現資料繫結技術對程式碼的改善,java程式碼中已經沒有了和View層相關的邏輯程式碼,直接在xml佈局中就可以完成,下面貼出xml佈局的關鍵程式碼:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="itemData"
            type="com.freelib.multiitem.demo.input.ItemInfoDataBind"/>
    </data>

    <LinearLayout ...>

        <TextView
            ...
            android:text="@{itemData.name}"/>

        <EditText
            ...
            //@={}為雙向繫結用法,即EditText的變化會實時更新到itemData.info屬性上
            android:text="@={itemData.info}"/>

    </LinearLayout>

</layout>複製程式碼

資料繫結的xml佈局和普通寫法也沒什麼差別,所以在這裡再次安利下,大家要多多使用DataBinding,提高開發效率,降低耦合度。

詳解

複用詳解

拿我們上面貼出程式碼的ItemEdit來說,在正常情況下所有EditText相關的錄入項都可以使用本類即可,這樣就做到了複用。所以我們在專案中封裝一些公用元件的錄入Item後,即使碰到大量到表單業務,變化再多都不需要擔心,只是在InputItemAdapter新增刪除一些元件Item或者把原有元件Item的順序調整一下即可,在這個過程中都不需要去碰到xml佈局檔案,在邏輯上也會比較清晰。

流程解析

這次實現相當於在原有功能的基礎上封裝了一些新的api,所以並沒有太多可以講解的地方,所以花了一個流程圖供大家參考:

用 RecyclerView 實現 Form 表單 靈活可複用 給你一個新思路 - MultiItem 進階
Form表單流程

總結

前言中也說利用RecyclerView實現Form表單了並不是一種主流的實現方式,當然會存在一些不足之處,但是比較適用於大量表單業務的客戶端中,希望大家多多交流!
最後擴充套件一下大家的思路,其實我們可以約定好表單格式資料,通過服務端下發,在客戶端做到動態表單錄入

相關文章