
前言
-
Android開發(fā)中,EditText的使用 非常常見(jiàn) - 本文將手把手教你做一款 附帶一鍵刪除功能 & 自定義樣式豐富的
SuperEditText控件,希望你們會(huì)喜歡。

已在
Github開源:Super_EditText,歡迎Star!
目錄

1. 簡(jiǎn)介
一款 附帶一鍵刪除功能 & 自定義樣式豐富的 SuperEditText控件
已在
Github開源:Super_EditText,歡迎Star!

2. 功能介紹
2.1 需求場(chǎng)景
對(duì)于 EditText來(lái)說(shuō),一般的需求有:
- 方便用戶因出現(xiàn)輸入錯(cuò)誤而進(jìn)行2次輸入
- 標(biāo)識(shí)用戶正在填寫項(xiàng)
- 根據(jù)具體場(chǎng)景增加一定的
UI元素
2.2 功能需求
根據(jù)需求場(chǎng)景,得出EditText需要具備的功能如下:
- 一鍵刪除
- 豐富的自定義樣式:左側(cè)圖標(biāo)、刪除功能圖標(biāo)、分割線 & 光標(biāo) 樣式變化。具體如下圖:

注:該樣式的設(shè)置是系統(tǒng)自帶的 API 所不具備的
- 功能列表

2.3 功能示意

3. 特點(diǎn)
對(duì)比市面上EditText控件,該控件Super_EditText 的特點(diǎn)是:
3.1 功能實(shí)用
- 一鍵刪除功能 在需求中非常常見(jiàn),現(xiàn)將其封裝后更加方便使用
- 可自定義樣式程度高(比自帶的強(qiáng)大 & 方便),不復(fù)雜卻能滿足一般的
EditText使用需求
可自定義樣式如下:(注:該樣式的設(shè)置是系統(tǒng)自帶的
API所不具備的)

3.2 使用簡(jiǎn)單
- 僅需要簡(jiǎn)單的
xml屬性配置 - 具體請(qǐng)看文章:Android自定義View:你需要一款簡(jiǎn)單實(shí)用的SuperEditText(一鍵刪除&自定義樣式)
3.3 二次開發(fā)成本低
- 本項(xiàng)目已在
Github上開源:Super_EditText - 具備詳細(xì)的源碼分析文檔(即本文)
所以,在其上做二次開發(fā) & 定制化成本非常低。
4. 功能詳細(xì)設(shè)計(jì)
下面將給出詳細(xì)的功能邏輯
4.1 一鍵清空輸入字段
- 描述:將當(dāng)前用戶輸入的字段清空
- 需求場(chǎng)景:方便用戶因出現(xiàn)輸入錯(cuò)誤而進(jìn)行2次輸入
- 原型圖

- 源碼分析
/*
* 步驟1:定義屬性
* */
private int ic_deleteResID; // 刪除圖標(biāo) 資源ID
private Drawable ic_delete; // 刪除圖標(biāo)
private int delete_x,delete_y,delete_width,delete_height; // 刪除圖標(biāo)起點(diǎn)(x,y)、刪除圖標(biāo)寬、高(px)
/*
* 步驟2:初始化屬性
* */
private void init(Context context, AttributeSet attrs) {
// 獲取控件資源
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SuperEditText);
/**
* 初始化刪除圖標(biāo)
*/
// 1. 獲取資源ID
ic_deleteResID = typedArray.getResourceId(R.styleable.SuperEditText_ic_delete,R.drawable.delete);
// 2. 根據(jù)資源ID獲取圖標(biāo)資源(轉(zhuǎn)化成Drawable對(duì)象)
ic_delete = getResources().getDrawable(ic_deleteResID);
// 3. 設(shè)置圖標(biāo)大小
// 起點(diǎn)(x,y)、寬= left_width、高 = left_height
delete_x = typedArray.getInteger(R.styleable.SuperEditText_delete_x, 0);
delete_y = typedArray.getInteger(R.styleable.SuperEditText_delete_y, 0);
delete_width = typedArray.getInteger(R.styleable.SuperEditText_delete_width, 60);
delete_height = typedArray.getInteger(R.styleable.SuperEditText_delete_height, 60);
ic_delete.setBounds(delete_x, delete_y, delete_width, delete_height);
/**
* 步驟3:通過(guò)監(jiān)聽復(fù)寫EditText本身的方法來(lái)確定是否顯示刪除圖標(biāo)
* 監(jiān)聽方法:onTextChanged() & onFocusChanged()
* 調(diào)用時(shí)刻:當(dāng)輸入框內(nèi)容變化時(shí) & 焦點(diǎn)發(fā)生變化時(shí)
*/
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
setDeleteIconVisible(hasFocus() && text.length() > 0,hasFocus());
// hasFocus()返回是否獲得EditTEXT的焦點(diǎn),即是否選中
// setDeleteIconVisible() = 根據(jù)傳入的是否選中 & 是否有輸入來(lái)判斷是否顯示刪除圖標(biāo)->>關(guān)注1
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
setDeleteIconVisible(focused && length() > 0,focused);
// focused = 是否獲得焦點(diǎn)
// 同樣根據(jù)setDeleteIconVisible()判斷是否要顯示刪除圖標(biāo)->>關(guān)注1
}
/**
* 關(guān)注1
* 作用:判斷是否顯示刪除圖標(biāo)
*/
private void setDeleteIconVisible(boolean deleteVisible,boolean leftVisible) {
setCompoundDrawables(leftVisible ? ic_left_click : ic_left_unclick, null,
deleteVisible ? ic_delete: null, null);
// setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)介紹
// 作用:在EditText上、下、左、右設(shè)置圖標(biāo)(相當(dāng)于android:drawableLeft="" android:drawableRight="")
// 備注:傳入的Drawable對(duì)象必須已經(jīng)setBounds(x,y,width,height),即必須設(shè)置過(guò)初始位置、寬和高等信息
// x:組件在容器X軸上的起點(diǎn) y:組件在容器Y軸上的起點(diǎn) width:組件的長(zhǎng)度 height:組件的高度
// 若不想在某個(gè)地方顯示,則設(shè)置為null
// 另外一個(gè)相似的方法:setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom)
// 作用:在EditText上、下、左、右設(shè)置圖標(biāo)
// 與setCompoundDrawables的區(qū)別:setCompoundDrawablesWithIntrinsicBounds()傳入的Drawable的寬高=固有寬高(自動(dòng)通過(guò)getIntrinsicWidth()& getIntrinsicHeight()獲?。? // 不需要設(shè)置setBounds(x,y,width,height)
}
/**
* 步驟4:對(duì)刪除圖標(biāo)區(qū)域設(shè)置點(diǎn)擊事件,即"點(diǎn)擊 = 清空搜索框內(nèi)容"
* 原理:當(dāng)手指抬起的位置在刪除圖標(biāo)的區(qū)域,即視為點(diǎn)擊了刪除圖標(biāo) = 清空搜索框內(nèi)容
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
// 原理:當(dāng)手指抬起的位置在刪除圖標(biāo)的區(qū)域,即視為點(diǎn)擊了刪除圖標(biāo) = 清空搜索框內(nèi)容
switch (event.getAction()) {
// 判斷動(dòng)作 = 手指抬起時(shí)
case MotionEvent.ACTION_UP:
Drawable drawable = ic_delete;
if (drawable != null && event.getX() <= (getWidth() - getPaddingRight())
&& event.getX() >= (getWidth() - getPaddingRight() - drawable.getBounds().width())) {
// 判斷條件說(shuō)明
// event.getX() :抬起時(shí)的位置坐標(biāo)
// getWidth():控件的寬度
// getPaddingRight():刪除圖標(biāo)圖標(biāo)右邊緣至EditText控件右邊緣的距離
// 即:getWidth() - getPaddingRight() = 刪除圖標(biāo)的右邊緣坐標(biāo) = X1
// getWidth() - getPaddingRight() - drawable.getBounds().width() = 刪除圖標(biāo)左邊緣的坐標(biāo) = X2
// 所以X1與X2之間的區(qū)域 = 刪除圖標(biāo)的區(qū)域
// 當(dāng)手指抬起的位置在刪除圖標(biāo)的區(qū)域(X2=<event.getX() <=X1),即視為點(diǎn)擊了刪除圖標(biāo) = 清空搜索框內(nèi)容
setText("");
}
break;
}
return super.onTouchEvent(event);
}

4.2 選中樣式
- 描述:通過(guò)增加UI元素 & 交互樣式表示用戶正在填寫的項(xiàng)目
- 需求場(chǎng)景:標(biāo)識(shí)用戶正在填寫項(xiàng)
- 樣式說(shuō)明

- 原型圖

- 屬性說(shuō)明


- 源碼分析
/*
* 步驟1:定義屬性
* */
private Paint mPaint; // 畫筆
private int ic_left_clickResID,ic_left_unclickResID; // 左側(cè)圖標(biāo) 資源ID(點(diǎn)擊 & 無(wú)點(diǎn)擊)
private Drawable ic_left_click,ic_left_unclick; // 左側(cè)圖標(biāo)(點(diǎn)擊 & 未點(diǎn)擊)
private int left_x,left_y,left_width,left_height; // 左側(cè)圖標(biāo)起點(diǎn)(x,y)、左側(cè)圖標(biāo)寬、高(px)
private int cursor; // 光標(biāo)
// 分割線變量
private int lineColor_click,lineColor_unclick;// 點(diǎn)擊時(shí) & 未點(diǎn)擊顏色
private int color;
private int linePosition; // 分割線位置
/*
* 步驟2:初始化屬性
* */
private void init(Context context, AttributeSet attrs) {
// 獲取控件資源
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SuperEditText);
/**
* 初始化左側(cè)圖標(biāo)(點(diǎn)擊 & 未點(diǎn)擊)
*/
// a. 點(diǎn)擊狀態(tài)的左側(cè)圖標(biāo)
// 1. 獲取資源ID
ic_left_clickResID = typedArray.getResourceId(R.styleable.SuperEditText_ic_left_click, R.drawable.ic_left_click);
// 2. 根據(jù)資源ID獲取圖標(biāo)資源(轉(zhuǎn)化成Drawable對(duì)象)
ic_left_click = getResources().getDrawable(ic_left_clickResID);
// 3. 設(shè)置圖標(biāo)大小
// 起點(diǎn)(x,y)、寬= left_width、高 = left_height
left_x = typedArray.getInteger(R.styleable.SuperEditText_left_x, 0);
left_y = typedArray.getInteger(R.styleable.SuperEditText_left_y, 0);
left_width = typedArray.getInteger(R.styleable.SuperEditText_left_width, 60);
left_height = typedArray.getInteger(R.styleable.SuperEditText_left_height, 60);
ic_left_click.setBounds(left_x, left_y,left_width, left_height);
// Drawable.setBounds(x,y,width,height) = 設(shè)置Drawable的初始位置、寬和高等信息
// x = 組件在容器X軸上的起點(diǎn)、y = 組件在容器Y軸上的起點(diǎn)、width=組件的長(zhǎng)度、height = 組件的高度
// b. 未點(diǎn)擊狀態(tài)的左側(cè)圖標(biāo)
// 1. 獲取資源ID
ic_left_unclickResID = typedArray.getResourceId(R.styleable.SuperEditText_ic_left_unclick, R.drawable.ic_left_unclick);
// 2. 根據(jù)資源ID獲取圖標(biāo)資源(轉(zhuǎn)化成Drawable對(duì)象)
// 3. 設(shè)置圖標(biāo)大小(此處默認(rèn)左側(cè)圖標(biāo)點(diǎn)解 & 未點(diǎn)擊狀態(tài)的大小相同)
ic_left_unclick = getResources().getDrawable(ic_left_unclickResID);
ic_left_unclick.setBounds(left_x, left_y,left_width, left_height);
/**
* 設(shè)置EditText左側(cè)圖片(初始狀態(tài)僅有左側(cè)圖片))
*/
setCompoundDrawables( ic_left_unclick, null,
null, null);
// setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom)介紹
// 作用:在EditText上、下、左、右設(shè)置圖標(biāo)(相當(dāng)于android:drawableLeft="" android:drawableRight="")
// 備注:傳入的Drawable對(duì)象必須已經(jīng)setBounds(x,y,width,height),即必須設(shè)置過(guò)初始位置、寬和高等信息
// x:組件在容器X軸上的起點(diǎn) y:組件在容器Y軸上的起點(diǎn) width:組件的長(zhǎng)度 height:組件的高度
// 若不想在某個(gè)地方顯示,則設(shè)置為null
// 另外一個(gè)相似的方法:setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, Drawable right, Drawable bottom)
// 作用:在EditText上、下、左、右設(shè)置圖標(biāo)
// 與setCompoundDrawables的區(qū)別:setCompoundDrawablesWithIntrinsicBounds()傳入的Drawable的寬高=固有寬高(自動(dòng)通過(guò)getIntrinsicWidth()& getIntrinsicHeight()獲?。? // 不需要設(shè)置setBounds(x,y,width,height)
/**
* 初始化光標(biāo)(顏色 & 粗細(xì))
*/
// 原理:通過(guò) 反射機(jī)制 動(dòng)態(tài)設(shè)置光標(biāo)
// 1. 獲取資源ID
cursor = typedArray.getResourceId(R.styleable.SuperEditText_cursor, R.drawable.cursor);
try {
// 2. 通過(guò)反射 獲取光標(biāo)屬性
Field f = TextView.class.getDeclaredField("mCursorDrawableRes");
f.setAccessible(true);
// 3. 傳入資源ID
f.set(this, cursor);
} catch (Exception e) {
e.printStackTrace();
}
/**
* 初始化分割線(顏色、粗細(xì)、位置)
*/
// 1. 設(shè)置畫筆
mPaint = new Paint();
mPaint.setStrokeWidth(2.0f); // 分割線粗細(xì)
// 2. 設(shè)置分割線顏色(使用十六進(jìn)制代碼,如#333、#8e8e8e)
int lineColorClick_default = context.getResources().getColor(R.color.lineColor_click); // 默認(rèn) = 藍(lán)色#1296db
int lineColorunClick_default = context.getResources().getColor(R.color.lineColor_unclick); // 默認(rèn) = 灰色#9b9b9b
lineColor_click = typedArray.getColor(R.styleable.SuperEditText_lineColor_click, lineColorClick_default);
lineColor_unclick = typedArray.getColor(R.styleable.SuperEditText_lineColor_unclick, lineColorunClick_default);
color = lineColor_unclick;
mPaint.setColor(lineColor_unclick); // 分割線默認(rèn)顏色 = 灰色
setTextColor(color); // 字體默認(rèn)顏色 = 灰色
// 3. 分割線位置
linePosition = typedArray.getInteger(R.styleable.SuperEditText_linePosition, 5);
// 消除自帶下劃線
setBackground(null);
/**
* 步驟3:通過(guò)監(jiān)聽復(fù)寫EditText本身的方法來(lái)設(shè)置所有樣式
* 監(jiān)聽方法:onTextChanged() & onFocusChanged()
* 調(diào)用時(shí)刻:當(dāng)輸入框內(nèi)容變化時(shí) & 焦點(diǎn)發(fā)生變化時(shí)
*/
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
setDeleteIconVisible(hasFocus() && text.length() > 0,hasFocus());
// hasFocus()返回是否獲得EditTEXT的焦點(diǎn),即是否選中
// setDeleteIconVisible() = 根據(jù)傳入的是否選中 & 是否有輸入來(lái)判斷是否顯示刪除圖標(biāo)->>關(guān)注1
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
setDeleteIconVisible(focused && length() > 0,focused);
// focused = 是否獲得焦點(diǎn)
// 同樣根據(jù)setDeleteIconVisible()判斷是否要顯示刪除圖標(biāo)->>關(guān)注1
}
/**
* 關(guān)注1
* 作用:設(shè)置分割線顏色
*/
private void setDeleteIconVisible(boolean deleteVisible,boolean leftVisible) {
color = leftVisible ? lineColor_click : lineColor_unclick;
setTextColor(color);
invalidate();
}
/**
* 步驟4:繪制分割線
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setColor(color);
setTextColor(color);
// 繪制分割線
// 需要考慮:當(dāng)輸入長(zhǎng)度超過(guò)輸入框時(shí),所畫的線需要跟隨著延伸
// 解決方案:線的長(zhǎng)度 = 控件長(zhǎng)度 + 延伸后的長(zhǎng)度
int x=this.getScrollX(); // 獲取延伸后的長(zhǎng)度
int w=this.getMeasuredWidth(); // 獲取控件長(zhǎng)度
// 傳入?yún)?shù)時(shí),線的長(zhǎng)度 = 控件長(zhǎng)度 + 延伸后的長(zhǎng)度
canvas.drawLine(0, this.getMeasuredHeight()- linePosition, w+x,
this.getMeasuredHeight() - linePosition, mPaint);
}
}
attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SuperEditText">
<attr name="ic_delete" format="reference" />
<attr name="delete_x" format="integer" />
<attr name="delete_y" format="integer" />
<attr name="delete_width" format="integer" />
<attr name="delete_height" format="integer" />
<attr name="ic_left_click" format="reference" />
<attr name="ic_left_unclick" format="reference" />
<attr name="left_x" format="integer" />
<attr name="left_y" format="integer" />
<attr name="left_width" format="integer" />
<attr name="left_height" format="integer" />
<attr name="lineColor_click" format="color" />
<attr name="lineColor_unclick" format="color" />
<attr name="linePosition" format="integer" />
<attr name="cursor" format="reference" />
</declare-styleable>
</resources>
cursor.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<solid android:color="@color/lineColor_click" />
<size android:width="1dp" />
</shape>
5. 完整源碼地址
Carson_Ho的Github地址:Super_EditText
6. 具體使用
具體請(qǐng)看文章:Android自定義View:你需要一款簡(jiǎn)單實(shí)用的SuperEditText(一鍵刪除&自定義樣式)
7. 貢獻(xiàn)代碼
- 希望你們能和我一起完善這款簡(jiǎn)單 & 好用的
SuperEditText控件,具體請(qǐng)看:貢獻(xiàn)代碼說(shuō)明 - 關(guān)于該開源項(xiàng)目的意見(jiàn) & 建議可在Issue上提出。歡迎
Star!
8. 總結(jié)
相信你一定會(huì)喜歡上 這款簡(jiǎn)單 & 好用的SuperEditText控件
已在
Github上開源:Super_EditText,歡迎Star!

Carson帶你學(xué)自定義View文章系列:
Carson帶你學(xué)自定義View:自定義View基礎(chǔ)
Carson帶你學(xué)自定義View:一文梳理自定義View工作流程
Carson帶你學(xué)自定義View:Measure過(guò)程
Carson帶你學(xué)自定義View:Layout過(guò)程
Carson帶你學(xué)自定義View:Draw過(guò)程
Carson帶你學(xué)自定義View:手把手教你寫一個(gè)完整的自定義View
Carson帶你學(xué)自定義View:Canvas類全面解析
Carson帶你學(xué)自定義View:Path類全面解析
歡迎關(guān)注Carson_Ho的簡(jiǎn)書
不定期分享關(guān)于安卓開發(fā)的干貨,追求短、平、快,但卻不缺深度。
