前言
本文是MultiItem
系列的進階文章社牲,講解如何利用RecyclerView
實現(xiàn)Form
表單陶夜,在日常開發(fā)中多數(shù)人還是使用普通布局方式實現(xiàn)二跋,這種方案比較直觀也很簡單,但是如果表單業(yè)務較多巾兆,并且易變,很多弊端就會顯現(xiàn)虎囚,不過這正是使用RecyclerView
實現(xiàn)的優(yōu)勢所在角塑,可以自定義一套通用的輸入類型的ItemInput
組件,既靈活又可復用淘讥。MultiItem
特點:
- 直接使用業(yè)務中的實體類為
RecyclerView Adapter
設置數(shù)據(jù)源圃伶,不需要做任何封裝 -
RecyclerView Adapter
零編碼,解放了復雜的Adapter
類 - 支持
DataBinding
蒲列,讓你清爽的編寫列表代碼 - 支持Form表單錄入窒朋,懶加載易復用,支持
DataBinding
蝗岖、隱藏域侥猩、輸入內(nèi)容驗證及是否變化
源碼地址
Github地址:https://github.com/free46000/MultiItem,請大家多多關注抵赢。
系列文章
- MultiItem用法與詳解-優(yōu)雅的實現(xiàn)多類型
- MultiItem進階 實現(xiàn)Head Foot和加載更多
- MultiItem進階 使用DataBinding讓RecyclerView代碼更簡潔清爽
- MultiItem擴展 仿任務面板 跨多個RecyclerView的Item拖動 支持縮小后拖動
效果截圖


用法
使用方法
首先初始化InputItemAdapter
欺劳,然后添加實現(xiàn)ItemInput
接口的數(shù)據(jù)源,相關代碼:
注:類庫中已提供了一些實現(xiàn)接口的基類如: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);
}
接下來展示提交表單的相關代碼邢享,提交時可以自動組裝數(shù)據(jù)鹏往,另外還提供了一些有用的api
,詳見代碼注釋:
public void submit() {
//通過adapter.isValueChange()判斷表單內(nèi)容是否改變
//通過adapter.isValueValid()判斷表單內(nèi)容是否有效
//通過adapter.getInputJson()直接獲取表單錄入Json骇塘,還有獲取錄入Map的方法
String tipTxt = "表單內(nèi)容" + (adapter.isValueChange() ? " 已經(jīng) " : " 沒有 ") +
"被用戶改變伊履!\n表單 " + (adapter.isValueValid() ? " 已經(jīng) " : " 沒有 ") +
"通過驗證!\n自動組裝的表單內(nèi)容為:\n";
//表單內(nèi)容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視圖不可以復用柔滔,所以直接在初始化視圖時設置好相關內(nèi)容即可
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
上面我們已經(jīng)看了普通錄入的實現(xiàn)溢陪,一對多錄入的方式需要在上面的基礎上,增加一些定制化的實現(xiàn)睛廊,所以和普通錄入重復的代碼就不貼出來了形真,只貼出一些關鍵的需要覆寫的方法,作用詳見注釋:
public class ItemNameAndSex extends BaseItemInput<ItemNameAndSex> {
//本例中需要返回兩組key-value所以去覆寫getValueMap()
@Override
public Object getValue() {
//在本方法中返回兩個值的組合超全,作用是為判斷表單的值是否被改變提供依據(jù)
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 數(shù)據(jù)綁定錄入ItemInfoDataBind
接下來我們看看數(shù)據(jù)綁定方式嘶朱,貼出關鍵代碼:
public class ItemInfoDataBind extends DataBindItemInput<ItemInfoDataBind> {
@Override
protected void initInputView(ViewDataBinding dataBinding) {
//把自身實例對象通過ViewDataBinding綁定到視圖中
dataBinding.setVariable(BR.itemData, this);
}
...
}
通過以上代碼我們不難發(fā)現(xiàn)數(shù)據(jù)綁定技術對代碼的改善蛾坯,java
代碼中已經(jīng)沒有了和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>
數(shù)據(jù)綁定的xml
布局和普通寫法也沒什么差別,所以在這里再次安利下财异,大家要多多使用DataBinding
,提高開發(fā)效率倘零,降低耦合度。
詳解
復用詳解
拿我們上面貼出代碼的ItemEdit
來說戳寸,在正常情況下所有EditText
相關的錄入項都可以使用本類即可呈驶,這樣就做到了復用。所以我們在項目中封裝一些公用組件的錄入Item
后疫鹊,即使碰到大量到表單業(yè)務袖瞻,變化再多都不需要擔心,只是在InputItemAdapter
添加刪除一些組件Item
或者把原有組件Item
的順序調(diào)整一下即可订晌,在這個過程中都不需要去碰到xml
布局文件虏辫,在邏輯上也會比較清晰。
流程解析
這次實現(xiàn)相當于在原有功能的基礎上封裝了一些新的api
锈拨,所以并沒有太多可以講解的地方,所以花了一個流程圖供大家參考:

總結
前言中也說利用RecyclerView
實現(xiàn)Form
表單了并不是一種主流的實現(xiàn)方式羹唠,當然會存在一些不足之處奕枢,但是比較適用于大量表單業(yè)務的客戶端中,希望大家多多交流佩微!
最后擴展一下大家的思路缝彬,其實我們可以約定好表單格式數(shù)據(jù),通過服務端下發(fā)哺眯,在客戶端做到動態(tài)表單錄入