Android form view use to forms
開發(fā)過程中遇到大量表單控件俊庇。故將原有項(xiàng)目中的表單抽離出來。
重構(gòu)代碼,精簡結(jié)構(gòu)。
抽離過程中遇到了一個(gè)小問題,后面將會(huì)講到泊藕。
希望能在使用本項(xiàng)目的過程中,幫助你更快的搭建表單頁面难礼。
省去時(shí)間用于實(shí)現(xiàn)業(yè)務(wù)邏輯娃圆,以及寫出更優(yōu)美簡潔的代碼玫锋。
如果任何問題,請?zhí)峤籌ssues,我將給予幫助讼呢。
如果給你啟示撩鹿,你也可以自己封裝自己業(yè)務(wù)中的代碼為組件庫,方便統(tǒng)一管理悦屏。
如何引用
```gradle
implementation 'com.markfrain.utils:formview:1.0.0'
```
TODO 1.自定義分割線
# 表單組件場景
適用于工作流节沦,業(yè)務(wù)流程頁面的,報(bào)表頁面的數(shù)據(jù)輸入础爬。
本項(xiàng)目抽離出最基礎(chǔ)的表單甫贯,以及統(tǒng)一樣式,多屬性設(shè)置,
使得UI組件呈現(xiàn)保持一致幕帆。
## 代碼結(jié)構(gòu)
```java
--package
|-utils
|-view
|-FormView //表單抽象基類
|-FormEditView//輸入框表單 其他變種輸入框都基于此類實(shí)現(xiàn)
|-FormEditMultiView //底部是輸入框的輸入框表單
|-FormEditTipsView //底部帶提示語的輸入框表單
|-FormImageView? //圖片表單
|-FormRadioView? //單選框表單
|-FormSwitchView //switch表單
|-FormViewInterface //表單接口方法 抽離出layoutId和initView
|-FormClickListener //表單點(diǎn)擊事件方法 FormLongClickListener繼承與此接口
```
## FormView
將所有表單抽離出來后获搏,可以看做4個(gè)控件赖条。
1.左側(cè)圖標(biāo)(例如:星號(hào),各種圖片)iv_left失乾。?
2.標(biāo)題(例如:姓名/朋友圈/設(shè)置)tv_title。?
3.表單內(nèi)容(例如:輸入框/圖片/文字/單選框/switch等)content纬乍。?
4.右側(cè)圖標(biāo)(例如:右箭頭/各種圖標(biāo))iv_right碱茁。?
子類都繼承于FormView,故都具備其所有屬性特征。
每個(gè)布局頁面的父控件都是LinearLayout仿贬。除開Parent Layout的padding外纽竣,
所有的邊距都是以右邊距來進(jìn)行設(shè)置。
FormView Attribute如下
|屬性字段名稱|屬性描述|備注|
|:-|:-|:-|
|fv_left_image|左側(cè)圖標(biāo)||
|fv_left_image_max_width|左側(cè)圖標(biāo)最大寬度||
|fv_left_image_max_height|左側(cè)圖標(biāo)最大高度||
|fv_left_image_right_margin|左側(cè)圖標(biāo)的右邊距||
|fv_left_image_scale_type|左側(cè)圖標(biāo)的縮放類型|與ImageView的scaleType一致|
|fv_right_image|右側(cè)圖標(biāo)||
|fv_right_image_max_width|右側(cè)圖標(biāo)最大寬度||
|fv_right_image_max_height|右側(cè)圖標(biāo)最大高度||
|fv_right_image_left_margin|右側(cè)圖標(biāo)最大寬度||
|fv_right_image_scale_type|右側(cè)圖標(biāo)的縮放類型|與ImageView的scaleType一致|
|fv_left_padding|表單左內(nèi)間距||
|fv_top_padding|表單上內(nèi)間距||
|fv_bottom_padding|表單下內(nèi)間距||
|fv_right_padding|表單右內(nèi)間距||
|fv_title|標(biāo)題內(nèi)容||
|fv_title_width|標(biāo)題寬度模式|分為wrap_content和weight_content,wrap_content可以結(jié)合fv_title_min_width使用得到固定寬度效果|
|fv_title_min_width|標(biāo)題寬度最小值||
|fv_title_text_size|標(biāo)題文字大小||
|fv_title_text_style|標(biāo)題文字Style|bold茧泪、italic蜓氨、normal|
|fv_title_text_width|標(biāo)題文字Panit的寬度|自定義寬度實(shí)現(xiàn)想要多粗就有多粗的效果|
|fv_title_right_margin|標(biāo)題文字的右邊距||
|fv_title_color|標(biāo)題文字的顏色||
FormView 內(nèi)部方法介紹
|方法名|用途|
|:-|:-|
|init(Context,AttributeSet)|初始化布局,以及獲取屬性|
|initView(View)|控件初始化findViewById|
|setMap(Map)|暴露外界傳參方法,FormClickListener和FormLongClickListener將把該值返回|
|setEnable(Boolean)|禁用方法,方便業(yè)務(wù)詳情頁面根據(jù)權(quán)限動(dòng)態(tài)使得控件是否能編輯/跳轉(zhuǎn)等|
|T getValue()|獲取控件的值队伟,如輸入框表單穴吹,將返回EditText的內(nèi)容,如FormRadioView將返回選中狀態(tài)|
|initCustom()|子類在重寫init(Context,AttributeSet),拓展子類的表現(xiàn)效果|
## FormEditView 輸入框表單和它的子類
表單內(nèi)容content是一個(gè)EditText。
根據(jù)場景不一樣嗜侮,可以是單行,多行,在底部等港令。
輸入框內(nèi)容可能為金額,重量,身高等等锈颗。
故組件還有一個(gè)tvUnit的TextView用于顯示單位顷霹。
FormEditView Attribute如下
|屬性字段名稱|屬性描述|備注|
|:-|:-|:-|
|fev_hint|輸入框提示語|
|fev_hint_color|輸入框提示語顏色|
|fev_text_gravity|輸入框文字內(nèi)容定位|top bottom 適合多行顯示配合fev_text_lines>1使用,left right 適合單行顯示|
|fev_text_lines|輸入框內(nèi)容行數(shù)|
|fev_text_color|輸入框內(nèi)容顏色|
|fev_text_size|輸入框文字大小|
|fev_text_max_length|輸入框內(nèi)容最大字?jǐn)?shù)|
|fev_text_inputType|輸入內(nèi)容類型|text、phone击吱、password淋淀、identifyCard(身份證號(hào))、number覆醇、decimal|
|fev_text_right_margin|輸入框右邊距|
|fev_text_bg|輸入框背景|
|fev_unit_text|單位文字|
|fev_unit_color|單位文字顏色|
|fev_unit_text_size|單位文字大小|
|fev_unit_visible|單位是否可見|
|fev_unit_right_margin|單位文字右邊距|
### FormEditMultiView 底部多行輸入框
此布局結(jié)構(gòu)為上下結(jié)構(gòu)绅喉。上面為左側(cè)圖標(biāo)渠鸽、標(biāo)題、右側(cè)圖標(biāo)柴罐。
下結(jié)構(gòu)為相對布局徽缚,其中有輸入框,字?jǐn)?shù)統(tǒng)計(jì)TextView革屠。
FormEditMultiView Attribute如下
|屬性字段名稱|屬性描述|備注|
|:-|:-|:-|
|femv_title_layout_left_padding|標(biāo)題布局左內(nèi)間距|
|femv_title_layout_top_padding|標(biāo)題布局上內(nèi)間距|
|femv_title_layout_right_padding|標(biāo)題布局右內(nèi)間距|
|femv_title_layout_bottom_padding|標(biāo)題布局下內(nèi)間距|
|femv_content_left_padding|輸入框左內(nèi)間距|
|femv_content_top_padding|輸入框上內(nèi)間距|
|femv_content_right_padding|輸入框右內(nèi)間距|
|femv_content_bottom_padding|輸入框下內(nèi)間距|
|femv_count_view_visible|字?jǐn)?shù)統(tǒng)計(jì)可見性|
|femv_count_left_text_color|字?jǐn)?shù)統(tǒng)計(jì)/左側(cè)文字顏色(不包含/)|
|femv_count_right_text_color|字?jǐn)?shù)統(tǒng)計(jì)/右側(cè)文字顏色(包含/)|
### FormEditTipsView 底部帶提示輸入框
此布局結(jié)構(gòu)為上下結(jié)構(gòu)凿试。上面為左側(cè)圖標(biāo)、標(biāo)題似芝、右側(cè)圖標(biāo)那婉。
下結(jié)構(gòu)為TextView顯示提示文字
FormEditTipsView Attribute如下
|屬性字段名稱|屬性描述|備注|
|:-|:-|:-|
|fetv_tips_top_margin|提示文字上邊距|
|fetv_tips_text|提示文字內(nèi)容|
|fetv_tips_text_color|提示文字顏色|
|fetv_tips_text_size|提示文字大小|
## FormImageView 圖片表單
屬性只有一個(gè)fiv_src.跟ImageView的src一致。
## FormRadioView
FormRadioView Attribute如下
|屬性字段名稱|屬性描述|備注|
|:-|:-|:-|
|frv_layout_min_width|RadioGroup的最小寬度党瓮。默認(rèn)是wrap_content|
|frv_text_gravity|RadioGroup內(nèi)容對齊方式left/right详炬。都是垂直居中的|
|frv_text_drawable_padding|單選按鈕的drawabPadding屬性|
|frv_text_drawable_left|單選左側(cè)按鈕圖標(biāo)|
|frv_text_drawable_top|單選上側(cè)按鈕圖標(biāo)|
|frv_text_drawable_right|單選右側(cè)按鈕圖標(biāo)|
|frv_text_drawable_bottom|單選下側(cè)按鈕圖標(biāo)|
|frv_left_to_right_margin|左邊單選按鈕的右邊距|
|frv_text_color|單選按鈕文字顏色|
|frv_text_size|單選按鈕文字大小|
|frv_left_text|左側(cè)單選按鈕文字內(nèi)容|
|frv_right_text|右側(cè)單選按鈕文字內(nèi)容|
## FormSwitchView
FormSwitchView Attribute如下
|屬性字段名稱|屬性描述|備注|
|:-|:-|:-|
|fsv_checkbox_drawable|CheckBox的drawableLeft|
|fsv_checkbox_right_margin|CheckBox的右邊距|
## 如果使用
根據(jù)上面的屬性解析,以及查看源碼。
每處代碼都有注釋,希望能幫到你寞奸。
```xml
<com.markfrain.formview.view.FormSwitchView
? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? ? android:paddingLeft="15dp"
? ? ? ? ? ? android:paddingTop="18dp"
? ? ? ? ? ? android:paddingRight="15dp"
? ? ? ? ? ? android:paddingBottom="18dp"
? ? ? ? ? ? app:fsv_checkbox_drawable="@drawable/checkbox_switch"
? ? ? ? ? ? app:fsv_checkbox_right_margin="10dp"
? ? ? ? ? ? app:fv_left_image="@drawable/ic_svg_star"
? ? ? ? ? ? app:fv_right_image="@drawable/ic_svg_right_arrow"
? ? ? ? ? ? app:fv_title="測試文本標(biāo)題"
? ? ? ? ? ? app:fv_title_right_margin="15dp"
? ? ? ? ? ? app:fv_title_width="weight_content" />
? ? ? ? <View
? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? android:layout_height="1px"
? ? ? ? ? ? android:background="@android:color/darker_gray" />
? ? ? ? <com.markfrain.formview.view.FormRadioView
? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? ? android:paddingLeft="15dp"
? ? ? ? ? ? android:paddingTop="18dp"
? ? ? ? ? ? android:paddingRight="15dp"
? ? ? ? ? ? android:paddingBottom="18dp"
? ? ? ? ? ? app:frv_layout_min_width="150dp"
? ? ? ? ? ? app:frv_left_to_right_margin="10dp"
? ? ? ? ? ? app:frv_text_color="@color/radio_text_color"
? ? ? ? ? ? app:frv_text_drawable_left="@drawable/radio_check"
? ? ? ? ? ? app:frv_text_drawable_padding="10dp"
? ? ? ? ? ? app:fv_left_image="@drawable/ic_svg_star"
? ? ? ? ? ? app:fv_right_image="@drawable/ic_svg_right_arrow"
? ? ? ? ? ? app:fv_title="測試文本標(biāo)題"
? ? ? ? ? ? app:fv_title_right_margin="15dp"
? ? ? ? ? ? app:fv_title_width="weight_content" />
? ? ? ? <View
? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? android:layout_height="1px"
? ? ? ? ? ? android:background="@android:color/darker_gray" />
? ? ? ? <com.markfrain.formview.view.FormImageView
? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? ? android:paddingLeft="15dp"
? ? ? ? ? ? android:paddingTop="18dp"
? ? ? ? ? ? android:paddingRight="15dp"
? ? ? ? ? ? android:paddingBottom="18dp"
? ? ? ? ? ? app:fiv_src="@mipmap/base_iv_header_member"
? ? ? ? ? ? app:fv_left_image="@drawable/ic_svg_star"
? ? ? ? ? ? app:fv_right_image="@drawable/ic_svg_right_arrow"
? ? ? ? ? ? app:fv_title="測試文本標(biāo)題"
? ? ? ? ? ? app:fv_title_right_margin="15dp" />
? ? ? ? <View
? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? android:layout_height="1px"
? ? ? ? ? ? android:background="@android:color/darker_gray" />
? ? ? ? <com.markfrain.formview.view.FormEditView
? ? ? ? ? ? android:layout_width="match_parent"
? ? ? ? ? ? android:layout_height="wrap_content"
? ? ? ? ? ? android:paddingLeft="15dp"
? ? ? ? ? ? android:paddingTop="18dp"
? ? ? ? ? ? android:paddingRight="15dp"
? ? ? ? ? ? android:paddingBottom="18dp"
? ? ? ? ? ? app:fev_hint="測試提示文字"
? ? ? ? ? ? app:fev_text_bg="@null"
? ? ? ? ? ? app:fev_text_gravity="top"
? ? ? ? ? ? app:fev_unit_text="單位"
? ? ? ? ? ? app:fv_left_image="@drawable/ic_svg_star"
? ? ? ? ? ? app:fv_right_image="@drawable/ic_svg_right_arrow"
? ? ? ? ? ? app:fv_title="測試文本標(biāo)題"
? ? ? ? ? ? app:fv_title_right_margin="15dp" />
```
## 關(guān)于RadioButton共用Drawable導(dǎo)致呛谜。切換展現(xiàn)效果出現(xiàn)問題的BUG。
當(dāng)前FormRadioView 有如下4個(gè)屬性枪萄。
```xml
<attr name="frv_text_drawable_left" format="reference" />
? ? ? ? <attr name="frv_text_drawable_top" format="reference" />
? ? ? ? <attr name="frv_text_drawable_right" format="reference" />
? ? ? ? <attr name="frv_text_drawable_bottom" format="reference" />
? ? ? ? <attr name="frv_left_to_right_margin" format="dimension" />
```
早之前我的代碼如下編寫隐岛,即獲取到Drawable后,兩個(gè)RadioButton同時(shí)設(shè)置Drawable.
```java
? ? rbLeft.setCompoundDrawablesWithIntrinsicBounds(leftDrawable, topDrawable, rightDrawable, bottomDrawable);
rbRight.setCompoundDrawablesWithIntrinsicBounds(leftDrawable, topDrawable, rightDrawable, bottomDrawable);
```
咋一看并沒有什么不對瓷翻。感興趣的朋友聚凹,你可以自己實(shí)現(xiàn)試試。
我這邊直接說結(jié)果:
1.設(shè)置同一個(gè)Drawable導(dǎo)致兩個(gè)RadioButton都持有Drawable的引用
2.在切換Drawable的時(shí)候齐帚,由于共用引用妒牙。進(jìn)而RadioButton的展示的Drawable的效果和其mChecked不一致。
為什么呢对妄?
1.首先Debug定位.由于設(shè)置的Drawable是一個(gè)selector的,
TypedArray.getDrawable之后獲取到的是StateListDrawable湘今。
2.其次RadioButton向上找父類。閱讀TextView源碼
關(guān)鍵代碼如下:
```java
public TextView(
? ? ? ? ? ? Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
? ? ? ? super(context, attrs, defStyleAttr, defStyleRes);
? ? ? ? ...
? ? ? ? //1.將TypedArray.getDrawable的drawableLeft, drawableTop, drawableRight, drawableBottom饥伊。設(shè)置給TextView
? ? ? ? setCompoundDrawablesWithIntrinsicBounds(
? ? ? ? ? ? ? ? drawableLeft, drawableTop, drawableRight, drawableBottom);
? ? }
? ? //2. setCompoundDrawablesWithIntrinsicBounds最后執(zhí)行到這個(gè)方法
? ? public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
? ? ? ? ? ? @Nullable Drawable right, @Nullable Drawable bottom) {
? ? ...
if (left != null) {
? ? ? ? ...
? ? ? ? left.setCallback(this);
? ? ? ? //3.這里有個(gè)setCallBack 看名字都知道是個(gè)回調(diào)函數(shù)
? ? ? ? //View源碼 中 public class View implements Drawable.Callback象浑。
? ? ? ? ...
? ? }
}
//4.再看看StateListDrawable這個(gè)類
//既然狀態(tài)混亂了,肯定是state相關(guān)的方法出現(xiàn)了問題
@Override
? ? protected boolean onStateChange(int[] stateSet) {
? ? ? ? //通過狀態(tài)獲取到狀態(tài)數(shù)組下標(biāo)
? ? ? ? int idx = mStateListState.indexOfStateSet(stateSet);
? ? ? ? ...
? ? ? ? //省去上面代碼琅豆,看關(guān)鍵的這句selectDrawable(idx);
? ? ? ? return selectDrawable(idx) || changed;
? ? }
? ? //5.通過selectDrawable跟蹤到DrawableContainer類
? ? public boolean selectDrawable(int index) {
? ? ...
? ? invalidateSelf();//看名字叫重繪自己
? ? }
? ? //6.跟蹤到Drawable類中
? ? public void invalidateSelf() {
? ? ? ? final Callback callback = getCallback();
? ? ? ? if (callback != null) {
? ? ? ? ? ? callback.invalidateDrawable(this);
? ? ? ? }
? ? }
? ? //7. TextView 的 invalidateDrawable覆蓋了View的該方法
? ? @Override
? ? public void invalidateDrawable(@NonNull Drawable drawable) {
? ? }
? ? //8. CompoundButton的源碼如下:
? ? @Override
? ? public void setChecked(boolean checked) {
? ? ...
? ? refreshDrawableState();
? ? ...
? ? }
? ? public void refreshDrawableState() {
? ? ? ? ...
? ? ? ? drawableStateChanged();
? ? ? ? ...
}
? ? //9. CompoundButton中覆寫了drawableStateChanged
@Override
? ? protected void drawableStateChanged() {
? ? ? ? super.drawableStateChanged();
? ? ? ? final Drawable buttonDrawable = mButtonDrawable;
? ? ? ? if (buttonDrawable != null && buttonDrawable.isStateful()
? ? ? ? ? ? ? ? && buttonDrawable.setState(getDrawableState())) {
? ? ? ? ? ? invalidateDrawable(buttonDrawable);
? ? ? ? }
? ? }
? ? //10.buttonDrawable.setState(getDrawableState())跟蹤到Drawable類
? ? //這里又回到了第4點(diǎn)
? ? public boolean setState(@NonNull final int[] stateSet) {
? ? ? ? if (!Arrays.equals(mStateSet, stateSet)) {
? ? ? ? ? ? mStateSet = stateSet;
? ? ? ? ? ? return onStateChange(stateSet);
? ? ? ? }
? ? ? ? return false;
? ? }
```
由此可以看出Drawable和CompoundButton通過CallBack關(guān)聯(lián)巫击。2者互相影響爱榕。
```java
//這里設(shè)置了StateListDrawable的CallBack為rbLeft
rbLeft.setCompoundDrawablesWithIntrinsicBounds(leftDrawable, topDrawable, rightDrawable, bottomDrawable);
//這里再次把 StateListDrawable的CallBack為rbRight
rbRight.setCompoundDrawablesWithIntrinsicBounds(leftDrawable, topDrawable, rightDrawable, bottomDrawable);
//由于上面源碼的分析滑臊。
//rbLeft 持有Drawable引用
//rbRight 持有Drawable引用牍疏。而Drawable的CallBack設(shè)置為rbRight。
//則就會(huì)導(dǎo)致亂序,出現(xiàn)以下怪異情況
//設(shè)置RadioGroup默認(rèn)選中rbLeft。
//1.點(diǎn)擊右側(cè)按鈕驰贷。選中狀態(tài)如下:?
// I/rbRight: true
// I/rbLeft: false
// 右側(cè)選中按鈕效果盛嘿,左側(cè)未選中按鈕效果。
//2.點(diǎn)擊左側(cè)按鈕 選中狀態(tài)如下:?
// I/rbLeft: true
// I/rbRight: false
//兩個(gè)RadioButton都選中了括袒。
//為啥次兆。因?yàn)閞bLeft改變了StateListDrawable的狀態(tài)。
//而StateListDrawable的CallBack綁定的rbRight锹锰。
//所以rbRight的顯示改變了芥炭,但狀態(tài)仍然為false。
```