簡介
在Android開發(fā)中除了View的自定義繪制以外加叁,組合控件也是比較常見的一種自定義View的方式哥力,這種方式通過把系統(tǒng)提供的一些常見View組合起來蔗怠,添加一些自定義的屬性;很少或者根本不需要進行手動的額外繪制省骂,操作相對簡單一些蟀淮,也能大幅度提升開發(fā)效率。
在絕大多數app中都有這樣的頁面:
需求整理
從上圖我們可以看到這種常見的UI效果中包含了幾種情況:
1.選擇效果钞澳,例如時間的選擇怠惶,點擊右邊彈出選擇框或者頁面跳轉,整個item是不可編輯的轧粟。
2.輸入效果策治,左邊是顯示輸入框的文本屬性,右邊是輸入框兰吟,用戶可以輸入文本通惫,例如修改個人信息的時候會用到。
3.顯示效果混蔼,左右都不可編輯履腋,只是單純的顯示一些信息。
除此之外還有一些細節(jié)方面的屬性惭嚣,例如:左邊和右邊文本的屬性遵湖,顏色,大小晚吞,padding值等延旧;item之間的divider分隔線,分隔線的顏色槽地,寬度迁沫,padding值芦瘾;文本框的hint以及hint的細節(jié)屬性;文字的Gravity屬性以及item的點擊事件響應等等集畅。
之前為了實現這些效果近弟,大多數人常用的方法就是直接在UI里面進行系統(tǒng)控件的嵌套組合;這是一種最直接也最簡單的方法牡整。但是如果類似的效果少了還好藐吮,多的話就讓人比較頭疼了,只能一遍一遍復制粘貼逃贝。這樣就會造成頁面布局文件很長很長谣辞;系統(tǒng)解析起來費勁,自己看著也煩沐扳。
這時候自定義組合控件就派上用場了泥从。
實現原理
先通過分析,把類似的效果抽取到一個通用的UI中沪摄。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/ll_view_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_choose_item_name"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="15dp"
android:textColor="@android:color/black"/>
<TextView
android:id="@+id/tv_choose_item_choose"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="100"
android:drawablePadding="15dp"
android:maxLines="1"
android:ellipsize="end"
android:padding="15dp"
android:textColor="@android:color/black"/>
<EditText
android:id="@+id/et_choose_item_choose"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="100"
android:background="@null"
android:paddingLeft="15dp"
android:paddingBottom="15dp"
android:paddingTop="15dp"
android:paddingRight="50dp"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@android:color/black"
android:visibility="gone"/>
</LinearLayout>
總的來說就是一個TextView+一個EditText的組合躯嫉,或者兩個TextView的組合;或者可以把右箭頭圖標單獨拎出來放進一個ImageView中杨拐。
布局抽取出來之后就開始進行控件組合代碼的編寫:
先繼承LinearLayout并添加構造方法祈餐,添加自定義的屬性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ChooseItemView">
<attr name="item_name_text" format="string|reference"/><!--item名稱-->
<attr name="item_name_text_padding_left" format="dimension|reference"/>
<attr name="item_height" format="dimension|reference"/>
<attr name="item_name_text_color" format="color|reference"/>
<attr name="item_name_text_size" format="dimension|reference"/>
<attr name="item_choose_text" format="string|reference"/>
<attr name="item_choose_text_color" format="color|reference"/>
<attr name="item_choose_text_size" format="dimension|reference"/>
<attr name="item_choose_icon_visible" format="boolean"/>
<attr name="item_edit_mode" format="boolean"/>
<attr name="item_edit_hint" format="string|reference"/>
<attr name="item_edit_text" format="string|reference"/>
<attr name="item_edit_text_size" format="dimension|reference"/>
<attr name="item_edit_text_color" format="color|reference"/>
<attr name="item_edit_hint_color" format="color|reference"/>
<attr name="item_divider_color" format="color|reference"/>
<attr name="item_divider_height" format="reference|dimension"/>
<attr name="item_divider_margin_left" format="reference|dimension"/>
<attr name="item_divider_margin_right" format="reference|dimension"/>
<attr name="item_choose_gravity" format="enum">
<enum name="left" value="0"/>
<enum name="center" value="1"/>
<enum name="right" value="2"/>
</attr>
<attr name="item_edit_text_gravity" format="enum">
<enum name="left" value="0"/>
<enum name="center" value="1"/>
<enum name="right" value="2"/>
</attr>
</declare-styleable>
</resources>
public class ChooseItemView extends LinearLayout
{
public ChooseItemView(Context context)
{
this(context, null, 0);
}
public ChooseItemView(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public ChooseItemView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
this.context = context;
//抽取需要的自定義屬性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ChooseItemView);
itemNameText = typedArray.getString(R.styleable.ChooseItemView_item_name_text);
itemNameTextPaddingLeft = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_name_text_padding_left, dip2px(15));
itemNameTextColor = typedArray.getColor(R.styleable.ChooseItemView_item_name_text_color, Color.BLACK);
itemNameTextSize = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_name_text_size, 16);
itemChooseText = typedArray.getString(R.styleable.ChooseItemView_item_choose_text);
itemChooseTextColor = typedArray.getColor(R.styleable.ChooseItemView_item_choose_text_color, Color.BLACK);
itemChooseTextSize = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_choose_text_size, 16);
itemChooseGravity = typedArray.getInt(R.styleable.ChooseItemView_item_choose_gravity, 2);
editTextGravity = typedArray.getInt(R.styleable.ChooseItemView_item_edit_text_gravity, 2);
iconVisible = typedArray.getBoolean(R.styleable.ChooseItemView_item_choose_icon_visible, true);
editMode = typedArray.getBoolean(R.styleable.ChooseItemView_item_edit_mode, false);
editHint = typedArray.getString(R.styleable.ChooseItemView_item_edit_hint);
editHintColor = typedArray.getColor(R.styleable.ChooseItemView_item_edit_hint_color, Color.LTGRAY);
editText = typedArray.getString(R.styleable.ChooseItemView_item_edit_text);
editTextColor = typedArray.getColor(R.styleable.ChooseItemView_item_edit_text_color, Color.BLACK);
editTextSize = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_edit_text_size, 16);
dividerColor = typedArray.getColor(R.styleable.ChooseItemView_item_divider_color, Color.LTGRAY);
dividerHeight = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_divider_height, dip2px(2));
dividerMarinLeft = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_divider_margin_left, dip2px(15));
dividerMarinRight = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_divider_margin_right, 0);
itemHeight = (int) typedArray.getDimension(R.styleable.ChooseItemView_item_height, dip2px(50))+dividerHeight/2;
typedArray.recycle();
}
進行子View的綁定和屬性初始化:
private void initView()
{
//準備繪制分隔線的畫筆
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setColor(dividerColor);
paint.setStrokeWidth(dividerHeight);
//子控件綁定
View view = LayoutInflater.from(context).inflate(R.layout.layout_choose_item, this);
llContainer = (LinearLayout) view.findViewById(R.id.ll_view_container);
tvItemName = (TextView) view.findViewById(R.id.tv_choose_item_name);
tvItemChoose = (TextView) view.findViewById(R.id.tv_choose_item_choose);
etItemChoose = (EditText) view.findViewById(R.id.et_choose_item_choose);
//初始化各個控件的屬性
tvItemName.setText(itemNameText);
tvItemName.setTextColor(itemNameTextColor);
tvItemName.setTextSize(itemNameTextSize);
tvItemName.setPadding(itemNameTextPaddingLeft, dip2px(15), dip2px(15), dip2px(15));
tvItemChoose.setText(itemChooseText);
tvItemChoose.setTextColor(itemChooseTextColor);
tvItemChoose.setTextSize(itemChooseTextSize);
//item整體高度的自定義
ViewGroup.LayoutParams params = llContainer.getLayoutParams();
params.height = itemHeight;
llContainer.setLayoutParams(params);
etItemChoose.setText(editText);
etItemChoose.setHint(editHint);
etItemChoose.setHintTextColor(editHintColor);
etItemChoose.setTextSize(editTextSize);
etItemChoose.setTextColor(editTextColor);
setEditMode(editMode);
setIconVisible(iconVisible);
setItemChooseGravity(itemChooseGravity);
setEditTextGravity(editTextGravity);
}
繪制item之間的分隔線:
@Override
protected void dispatchDraw(Canvas canvas)
{
//繪制分隔線 一定要在父控件繪制完成之后
super.dispatchDraw(canvas);
canvas.drawLine(dividerMarinLeft, itemHeight, getWidth() - dividerMarinRight, itemHeight, paint);
}
添加一些常用的api以便在代碼中修改相關屬性:
//自定義屬性相關的api
public String getItemNameText()
{
return itemNameText;
}
public void setItemNameText(String itemNameText)
{
this.itemNameText = itemNameText;
tvItemName.setText(itemNameText);
}
public void setItemNameTextSize(int itemNameTextSize)
{
this.itemNameTextSize = itemNameTextSize;
tvItemName.setTextSize(TypedValue.COMPLEX_UNIT_SP, itemNameTextSize);
}
public void setItemChooseTextColor(int itemChooseTextColor)
{
this.itemChooseTextColor = itemChooseTextColor;
tvItemChoose.setTextColor(ContextCompat.getColor(context, itemChooseTextColor));
}
public int getItemChooseTextSize()
{
return itemChooseTextSize;
}
public void setItemChooseTextSize(int itemChooseTextSize)
{
this.itemChooseTextSize = itemChooseTextSize;
tvItemChoose.setTextSize(TypedValue.COMPLEX_UNIT_SP, itemChooseTextSize);
}
//與繪制相關的屬性修改完成后要記得通知系統(tǒng)進行重繪
public void setDividerColor(int dividerColor)
{
this.dividerColor = dividerColor;
paint.setColor(ContextCompat.getColor(context, dividerColor));
invalidate();
}
//根據需要添加對應的監(jiān)聽事件
public void setIconClickListener(OnClickListener iconClickListener)
{
tvItemChoose.setOnClickListener(iconClickListener);
}
public void setTextChangeListener(TextWatcher textChangeListener)
{
etItemChoose.addTextChangedListener(textChangeListener);
}
}
....
效果和總結
使用:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fff"
android:orientation="vertical">
<com.mewlxy.itemchooseview.view.ChooseItemView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:item_choose_text="2017-08-27"
app:item_name_text="選擇時間"/>
<com.mewlxy.itemchooseview.view.ChooseItemView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:item_edit_hint="請輸入姓名"
app:item_edit_mode="true"
app:item_name_text="輸入姓名"/>
<com.mewlxy.itemchooseview.view.ChooseItemView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:item_divider_color="@android:color/holo_purple"
app:item_divider_height="10dp"
app:item_divider_margin_left="0dp"
app:item_edit_hint="請輸入姓名"
app:item_edit_hint_color="#66ccbb"
app:item_edit_mode="true"
app:item_name_text="輸入姓名"
app:item_name_text_color="#66ccff"/>
<com.mewlxy.itemchooseview.view.ChooseItemView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:item_choose_gravity="left"
app:item_choose_icon_visible="false"
app:item_choose_text="姓名"
app:item_choose_text_color="@color/colorAccent"
app:item_divider_margin_left="0dp"
app:item_edit_hint_color="#66ccbb"
app:item_name_text="輸入姓名"
app:item_name_text_color="#66ccff"/>
</LinearLayout>
這樣完了之后,一個通用的UI控件就完成了哄陶,可以適用于大多數類似的頁面帆阳,僅僅需要設置幾個不同的屬性;成倍的減少了代碼量屋吨,布局文件再也不會像以前那樣臃腫了蜒谤。并且代碼的擴展性也增強了不少。以后遇到類似的場景就可以參考這種實現方式至扰,而不必一次次的復制粘貼鳍徽。