Android開(kāi)源框架PowerfulViewLibrary——PowerfulEditText的介紹###
??很高興自己寫(xiě)的前兩篇博客都得到了郭霖(人稱郭神)的認(rèn)可,答應(yīng)幫我發(fā)布在他的微信公眾號(hào)上,這讓我更有決心寫(xiě)好博客。我寧愿一個(gè)月一更,也不愿濫竽充數(shù),寫(xiě)博客一方面是為了分享自己的技術(shù)和經(jīng)驗(yàn),另一方面是為了可以互相學(xué)習(xí)、交流和進(jìn)步。最近決定開(kāi)發(fā)一個(gè)開(kāi)源框架,叫做PowerfulViewLibrary,也就是功能強(qiáng)大的View庫(kù),主要是為了方便開(kāi)發(fā),封裝一些常用的控件,下面是相關(guān)介紹和使用。
PowerfulEditText具有的功能###
1.自帶清除文本功能
??PowerfulEditText自帶清除文本功能,只需在布局文件該View屬性中添加funcType,指定為canClear,就可以自帶清除文本功能,使用如下:
<?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:orientation="vertical"
>
<com.chaychan.viewlib.PowerfulEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:funcType="canClear"
/>
</LinearLayout>
運(yùn)行后,效果如下:
??上圖所示的刪除圖標(biāo)是默認(rèn)的,當(dāng)然也可以指定右側(cè)刪除按鈕的圖標(biāo),只需添加多drawableRight屬性,這里建議使用一個(gè)selector,分別為普通狀態(tài)和按壓狀態(tài)設(shè)置一張圖片,這樣當(dāng)按壓圖標(biāo)的時(shí)候,會(huì)有一種按壓的狀態(tài),selector的編寫(xiě)如下:
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="按壓后的圖標(biāo)" />
<item android:drawable="普通狀態(tài)的圖標(biāo)" />
</selector>
源碼分析:
public class PowerfulEditText extends EditText {
/**普通類型*/
private static final int TYPE_NORMAL = -1;
/**自帶清除功能的類型*/
private static final int TYPE_CAN_CLEAR = 0;
/**自帶密碼查看功能的類型*/
private static final int TYPE_CAN_WATCH_PWD = 1;
public PowerfulEditText(Context context) {
this(context, null);
}
public PowerfulEditText(Context context, AttributeSet attrs) {
//這里構(gòu)造方法也很重要,不加這個(gè)很多屬性不能在XML里面定義
this(context, attrs, android.R.attr.editTextStyle);
}
public PowerfulEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
ta = context.obtainStyledAttributes(attrs, R.styleable.PowerfulEditText);
funcType = ta.getInt(R.styleable.PowerfulEditText_funcType, TYPE_NORMAL);
...
init();
}
}
??PowerfulEditText繼承于EditText,這里定義了三種類型,分別是普通類型、自帶清除功能類型以及自帶查看密碼的功能,其中第二個(gè)構(gòu)造方法,也就是只有兩個(gè)參數(shù)的構(gòu)造方法,里面調(diào)用該類的第三個(gè)構(gòu)造方法(帶三個(gè)參數(shù)的構(gòu)造方法),需要傳多一個(gè)int類型的參數(shù),這里不能像以前定義其他View的時(shí)候那樣直接傳一個(gè)0,而是要傳android.R.attr.editTextStyle,否則很多屬性不能在XML里面定義。自己的邏輯操作init()方法,在第三個(gè)構(gòu)造方法調(diào)用即可。
private void init() {
//獲取EditText的DrawableRight,假如沒(méi)有設(shè)置我們就使用默認(rèn)的圖片,左上右下
mRightDrawable = getCompoundDrawables()[2];
if (mRightDrawable == null) {
//如果右側(cè)沒(méi)有圖標(biāo)
if (funcType == TYPE_CAN_CLEAR) {
//有清除功能,設(shè)置默認(rèn)叉號(hào)選擇器
mRightDrawable = getResources().getDrawable(R.drawable.delete_selector);
}
}
//如果是清除功能,則一開(kāi)始隱藏右側(cè)默認(rèn)圖標(biāo),否則不隱藏右側(cè)默認(rèn)圖標(biāo)
setRightIconVisible(funcType == 0 ? false : true);
//設(shè)置輸入框里面內(nèi)容發(fā)生改變的監(jiān)聽(tīng)
addTextChangedListener(new TextWatcher() {
/**
* 當(dāng)輸入框里面內(nèi)容發(fā)生變化的時(shí)候回調(diào)的方法
*/
@Override
public void onTextChanged(CharSequence s, int start, int count,
int after) {
//如果是帶有清除功能的類型,當(dāng)文本內(nèi)容發(fā)生變化的時(shí)候,根據(jù)內(nèi)容的長(zhǎng)度是否為0進(jìn)行隱藏或顯示
if (funcType == 0) {
setRightIconVisible(s.length() > 0);
}
if (textListener != null) {
textListener.onTextChanged(s, start, count, after);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
if (textListener != null) {
textListener.beforeTextChanged(s, start, count, after);
}
}
@Override
public void afterTextChanged(Editable s) {
if (textListener != null) {
textListener.afterTextChanged(s);
}
}
});
}
/**
* 設(shè)置右側(cè)圖標(biāo)的顯示與隱藏,調(diào)用setCompoundDrawables為EditText繪制上去
*
* @param visible
*/
protected void setRightIconVisible(boolean visible) {
Drawable right = visible ? mRightDrawable : null;
setCompoundDrawables(getCompoundDrawables()[0],
getCompoundDrawables()[1], right, getCompoundDrawables()[3]);
}
/**
* 輸入框文本變化的回調(diào),如果需要進(jìn)行多一些操作判斷,則設(shè)置此listen替代*TextWatcher
*/
public interface TextListener {
void onTextChanged(CharSequence s, int start, int count, int after);
void beforeTextChanged(CharSequence s, int start, int count, int after);
void afterTextChanged(Editable s);
}
??在init()方法中,我們首先獲取到輸入框右側(cè)的Drawable對(duì)象,通過(guò)getCompoundDrawables()[2]獲取,getCompoundDrawables()方法是用于獲取輸入框四個(gè)方向圖標(biāo)的方法,返回的類型是一個(gè)Drawable數(shù)組,數(shù)組的元素排序分別是左(0)上(1)右(2)下(3)四個(gè)Drawable,這里我們需要獲取右側(cè)的圖標(biāo)對(duì)象,對(duì)應(yīng)的下標(biāo)為2。先判斷右側(cè)圖標(biāo)mRightDrawable是否為空,如果為空,并且當(dāng)前的功能類型為自帶清除功能,則使用默認(rèn)的清除圖標(biāo),通過(guò)調(diào)用getResources().getDrawable()加載對(duì)應(yīng)的selector。
??通過(guò)判斷功能類型是否屬于帶有清除功能的類型,設(shè)置右側(cè)圖標(biāo)初始化的時(shí)候是否顯示,接著設(shè)置文本內(nèi)容變化的監(jiān)聽(tīng),通過(guò)監(jiān)聽(tīng)文本內(nèi)容的變化,判斷右側(cè)圖標(biāo)是否要顯示。由于此處已經(jīng)設(shè)置TextWatcher進(jìn)行文本的監(jiān)聽(tīng),并且在onTextChanged()方法中進(jìn)行了操作,所以在使用PowfulEditText的時(shí)候,如果需要在文本內(nèi)容監(jiān)聽(tīng)中做相應(yīng)操作,則需要使用自己定義的TextListener來(lái)進(jìn)行回調(diào),而不是使用TextWatcher。
??關(guān)于輸入框右側(cè)圖標(biāo)點(diǎn)擊事件的處理,所采用的方法是重寫(xiě)onTouchEvent()方法,對(duì)觸摸響應(yīng)進(jìn)行處理,判斷觸摸的位置是否落在右側(cè)圖標(biāo)的范圍內(nèi),如果是,則認(rèn)為是點(diǎn)擊了該圖標(biāo)。
/**
* 因?yàn)槲覀儾荒苤苯咏oEditText設(shè)置點(diǎn)擊事件,所以我們用記住我們按下的位置來(lái)模擬點(diǎn)擊事件
* 當(dāng)我們按下的位置 在 EditText的寬度 - 圖標(biāo)到控件右邊的間距 - 圖標(biāo)的寬度 和
* EditText的寬度 - 圖標(biāo)到控件右邊的間距之間我們就算點(diǎn)擊了圖標(biāo),豎直方向就沒(méi)有考慮
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (getCompoundDrawables()[2] != null) {
boolean isTouched = event.getX() > (getWidth() - getTotalPaddingRight())
&& (event.getX() < ((getWidth() - getPaddingRight())));
if (isTouched) {
if (onRightClickListener == null) {
if (funcType == TYPE_CAN_CLEAR) {
//如果沒(méi)有設(shè)置右邊圖標(biāo)的點(diǎn)擊事件,并且?guī)в星宄δ埽J(rèn)清除文本
this.setText("");
} else if (funcType == TYPE_CAN_WATCH_PWD) {
//如果沒(méi)有設(shè)置右邊圖標(biāo)的點(diǎn)擊事件,并且?guī)в胁榭疵艽a功能,點(diǎn)擊切換密碼查看方式
...
}
} else {
//如果有則回調(diào)
...
}
}
}
}
return super.onTouchEvent(event);
}
??當(dāng)按下的x值在EditText的寬度 - (圖標(biāo)到控件右邊的間距 + 圖標(biāo)的寬度)(getTotalPaddingRight()) 和 EditText的寬度 - 圖標(biāo)到控件右邊的間距之間,則認(rèn)為是點(diǎn)擊了圖標(biāo)(如果為了精確計(jì)算,可以考慮y值方向的判斷)然后進(jìn)行相應(yīng)的操作,如果沒(méi)有設(shè)置右側(cè)圖標(biāo)的點(diǎn)擊事件,并且當(dāng)前屬于帶有清除功能類型,則默認(rèn)清除文本。
2.自帶密碼輸入框切換明文密文格式的功能
??PowerfulEditText自帶密碼輸入框切換明文密文格式的功能,目前大多數(shù)App密碼輸入欄一般支持密碼明文、密文的顯示,如果需要用到該功能,可以將funcType中指定為canWatchPwd,就可以輕松使用這種功能,使用如下:
<com.chaychan.viewlib.PowerfulEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:funcType="canWatchPwd"
android:inputType="textPassword"
/>
運(yùn)行后,效果如下:
源碼解析:
??同樣也是在onTouchEvent()方法中做處理,如果當(dāng)前的功能類型屬于查看密碼功能類型,則根據(jù)eyeOpen這個(gè)標(biāo)識(shí)進(jìn)行判斷,判斷當(dāng)前是否是明文,如果是,點(diǎn)擊后則變成密文,否則變成明文。
/**
* 因?yàn)槲覀儾荒苤苯咏oEditText設(shè)置點(diǎn)擊事件,所以我們用記住我們按下的位置來(lái)模擬點(diǎn)擊事件
* 當(dāng)我們按下的位置 在 EditText的寬度 - 圖標(biāo)到控件右邊的間距 - 圖標(biāo)的寬度 和
* EditText的寬度 - 圖標(biāo)到控件右邊的間距之間我們就算點(diǎn)擊了圖標(biāo),豎直方向就沒(méi)有考慮
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (getCompoundDrawables()[2] != null) {
boolean isTouched = event.getX() > (getWidth() - getTotalPaddingRight())
&& (event.getX() < ((getWidth() - getPaddingRight())));
if (isTouched) {
if (onRightClickListener == null) {
if (funcType == TYPE_CAN_CLEAR) {
//如果沒(méi)有設(shè)置右邊圖標(biāo)的點(diǎn)擊事件,并且?guī)в星宄δ?,默認(rèn)清除文本
...
} else if (funcType == TYPE_CAN_WATCH_PWD) {
//如果沒(méi)有設(shè)置右邊圖標(biāo)的點(diǎn)擊事件,并且?guī)в胁榭疵艽a功能,點(diǎn)擊切換密碼查看方式
if (eyeOpen) {
//變?yōu)槊芪?
this.setTransformationMethod(PasswordTransformationMethod.getInstance());
eyeOpen = false;
} else {
//變?yōu)槊魑? this.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
eyeOpen = true;
}
switchWatchPwdIcon();//切換圖標(biāo)
}
} else {
//如果有則回調(diào)
...
}
}
}
}
return super.onTouchEvent(event);
}
/**
* 切換查看密碼的圖標(biāo)
*/
private void switchWatchPwdIcon() {
if (eyeOpen) {
//開(kāi)啟查看
setCompoundDrawables(getCompoundDrawables()[0],
getCompoundDrawables()[1], mEyeOpenDrawable, getCompoundDrawables()[3]);
} else {
//關(guān)閉查看
setCompoundDrawables(getCompoundDrawables()[0],
getCompoundDrawables()[1], mRightDrawable, getCompoundDrawables()[3]);
}
}
關(guān)于輸入框明文和密文切換的設(shè)置有兩種方法。
方法一:
setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);//密文密碼
setInputType(EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); //明文密碼
方法二:
setTransformationMethod(PasswordTransformationMethod.getInstance());//密文密碼
setTransformationMethod(HideReturnsTransformationMethod.getInstance());//明文密碼
??上述兩種方法都可以實(shí)現(xiàn)明文密文的切換,但是方法一切換后EditText的光標(biāo)會(huì)回到最左側(cè),所以這里選擇使用方法二,個(gè)人覺(jué)得體驗(yàn)比較好一些。
??上圖所示的右側(cè)圖標(biāo)是默認(rèn)的,同樣也可以指定開(kāi)啟查看密碼的圖標(biāo)和關(guān)閉查看密碼的圖標(biāo),只需要在屬性eyeOpen中指定開(kāi)啟查看密碼引用的圖片,在eyeClosed中指定關(guān)閉查看密碼引用的圖片即可,如下,更換開(kāi)啟查看密碼的圖標(biāo),如項(xiàng)目默認(rèn)的圖標(biāo)ic_launcher
<com.chaychan.viewlib.PowerfulEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:funcType="canWatchPwd"
android:inputType="textPassword"
app:eyeOpen="@mipmap/ic_launcher"
/>
運(yùn)行后,效果如下:
這樣開(kāi)啟查看密碼的圖標(biāo)就更換了,如果還需要更換關(guān)閉密碼查看的圖標(biāo),可以指定eyeClose,引用對(duì)應(yīng)的圖標(biāo)。
3.設(shè)置drawableLeft和drawableRight圖片大小的功能
??原生的EditText并不能在屬性中指定drawableLeft或drawableRight圖片的大小,所以一般開(kāi)發(fā)的過(guò)程中,一些程序員會(huì)采用簡(jiǎn)單粗暴的方法,直接引用一張寬高都很小的圖片。但是在不同屏幕分辨率下,兼容性就不是很好,比如在一些屏幕分辨率較高的手機(jī)上運(yùn)行,圖標(biāo)會(huì)顯得模糊。PowerfulEditText可以指定drawableLeft和drawableRight圖片的寬高大小,可以指定為多少個(gè)dp,這樣在開(kāi)發(fā)的時(shí)候,可以在各個(gè)分辨率圖片文件夾中放入不同尺寸的圖標(biāo),通過(guò)設(shè)定圖片的寬高屬性來(lái)限制顯示的大小,下面演示一下:
<com.chaychan.viewlib.PowerfulEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:funcType="canWatchPwd"
android:inputType="textPassword"
android:drawableLeft="@mipmap/ic_launcher"
/>

??如圖,指定了drawableLeft的圖片為ic_laucher,圖片看起來(lái)比較大,這時(shí)如果我們想要將其調(diào)小,則可以添加leftDrawableWidth、leftDrawableHeight指定左側(cè)圖片的寬高。
<com.chaychan.viewlib.PowerfulEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:funcType="canWatchPwd"
android:inputType="textPassword"
android:drawableLeft="@mipmap/ic_launcher"
app:leftDrawableWidth="30dp"
app:leftDrawableHeight="30dp"
/>
上面代碼,指定了leftDrawableWidth和leftDrawableHeight的大小都為30dp,運(yùn)行的效果如下:

可以看到左側(cè)的圖標(biāo)變小了,同樣也可以設(shè)置右側(cè)圖片的寬高,對(duì)應(yīng)的屬性是rightDrawableWidth、rightDrawableHeight。
??源碼解析:
public PowerfulEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
ta = context.obtainStyledAttributes(attrs, R.styleable.PowerfulEditText);
...
init();
}
if (leftDrawable != null) {
leftWidth = ta.getDimensionPixelOffset(R.styleable.PowerfulEditText_leftDrawableWidth,leftDrawable.getIntrinsicWidth());
leftHeight = ta.getDimensionPixelOffset(R.styleable.PowerfulEditText_leftDrawableHeight,leftDrawable.getIntrinsicHeight());
leftDrawable.setBounds(0, 0, leftWidth, leftHeight);
}
if (mRightDrawable != null) {
rightWidth = ta.getDimensionPixelOffset(R.styleable.PowerfulEditText_rightDrawableWidth,mRightDrawable.getIntrinsicWidth());
rightHeight = ta.getDimensionPixelOffset(R.styleable.PowerfulEditText_rightDrawableWidth,mRightDrawable.getIntrinsicHeight());
mRightDrawable.setBounds(0, 0, rightWidth, rightHeight);
if (mEyeOpenDrawable != null) {
mEyeOpenDrawable.setBounds(0, 0, rightWidth, rightHeight);
}
...
}
??這里通過(guò)TypedArray獲取XML中配置的對(duì)應(yīng)leftDrawableWidth、leftDrawableHeight、rightDrawableWidth、rightDrawableHeight的尺寸大小,如果沒(méi)有設(shè)置這些屬性,則默認(rèn)使用圖標(biāo)的寬和高,然后通過(guò)Drawable.setBounds()方法,設(shè)置右側(cè)圖標(biāo)的寬高,達(dá)到改變兩側(cè)圖標(biāo)大小的目的。
設(shè)置右側(cè)圖標(biāo)點(diǎn)擊事件####
PowerfulEditText同樣支持右側(cè)圖片的點(diǎn)擊事件,如果funcType指定為canClear,則默認(rèn)點(diǎn)擊是清除文本。如果需要進(jìn)行一些額外的操作,則可以設(shè)置回調(diào),比如搜索輸入框,右側(cè)是一個(gè)搜索的按鈕,需要為其設(shè)置點(diǎn)擊事件的回調(diào)。
布局文件:
<com.chaychan.viewlib.PowerfulEditText
android:id="@+id/pet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableRight="@mipmap/search"
/>
Activity
PowerfulEditText petUsername = (PowerfulEditText) findViewById(R.id.pet);
petUsername.setOnRightClickListener(new PowerfulEditText.OnRightClickListener() {
@Override
public void onClick(EditText editText) {
String content = editText.getText().toString().trim();
if (!TextUtils.isEmpty(content)){
Toast.makeText(MainActivity.this, "執(zhí)行搜索邏輯", Toast.LENGTH_SHORT).show();
}
}
});
運(yùn)行效果如下:

??上面布局文件中,和普通的EditText屬性一樣,funcType一共有三個(gè)屬性,分別是normal(默認(rèn))、canClear(帶清除功能)、canWatchPwd(帶查看密碼功能)。如果不指定funcType,則默認(rèn)是normal,普通方式。
??Activity中,為PowerfulEditText設(shè)置右側(cè)圖片的點(diǎn)擊事件,調(diào)用setOnRightClickListener設(shè)置點(diǎn)擊后的回調(diào),這里點(diǎn)擊后如果有文本內(nèi)容,則執(zhí)行搜索邏輯。
??這里僅對(duì)EditText的一些功能進(jìn)行封裝,對(duì)于樣式的更改,就要靠開(kāi)發(fā)者根據(jù)自己的需求進(jìn)行修改,修改的方式也不難,只要將background指定為自己編寫(xiě)的的xml即可。
??關(guān)于右側(cè)圖標(biāo)點(diǎn)擊事件的回調(diào),和上面所講到的是一致的,是在onTouchEvent()方法中,判斷觸摸范圍是否落在右側(cè)圖標(biāo)上,然后判斷OnRightClickListener是否為null,如果不為null,則進(jìn)行回調(diào)。OnRightClickListener接口很簡(jiǎn)單,回調(diào)的時(shí)候傳遞當(dāng)前的EditText對(duì)象,如果需要對(duì)右側(cè)圖標(biāo)點(diǎn)擊事件進(jìn)行自己的邏輯處理,則通過(guò)調(diào)用setOnRightClickListener()進(jìn)行回調(diào)。
/**
* 右邊圖標(biāo)點(diǎn)擊的回調(diào)
*/
public interface OnRightClickListener {
void onClick(EditText editText);
}
??關(guān)于PowerfulEditText的相關(guān)屬性,可以通過(guò)查看attr.xml便一目了然,如下:
<declare-styleable name="PowerfulEditText">
<!--功能的類型-->
<attr name="funcType">
<enum name="normal" value="-1"/>
<enum name="canClear" value="0"/>
<enum name="canWatchPwd" value="1" />
</attr>
<!--關(guān)閉查看密碼的圖標(biāo)-->
<attr name="eyeClose" format="reference"/>
<!--開(kāi)啟查看密碼的圖標(biāo)-->
<attr name="eyeOpen" format="reference"/>
<!--左側(cè)Drawable的寬度-->
<attr name="leftDrawableWidth" format="dimension"/>
<!--左側(cè)Drawable的高度-->
<attr name="leftDrawableHeight" format="dimension"/>
<!--右側(cè)Drawable的寬度-->
<attr name="rightDrawableWidth" format="dimension"/>
<!--右側(cè)Drawable的高度-->
<attr name="rightDrawableHeight" format="dimension"/>
</declare-styleable>
導(dǎo)入方式####
在項(xiàng)目根目錄下的build.gradle中的allprojects{}中,添加jitpack倉(cāng)庫(kù)地址,如下:
allprojects {
repositories {
jcenter()
maven { url 'https://jitpack.io' }//添加jitpack倉(cāng)庫(kù)地址
}
}
打開(kāi)app的module中的build.gradle,在dependencies{}中,添加依賴,如下:
dependencies {
......
compile 'com.github.chaychan:PowerfulViewLibrary:1.0'
}
??這樣就可以使用PowerfulViewLibrary下的控件了,目前只對(duì)EditText的一些功能進(jìn)行了封裝,往后會(huì)把一些常用的View進(jìn)行封裝,方便項(xiàng)目的開(kāi)發(fā),我會(huì)保持對(duì)PowerfulViewLibrary的更新和維護(hù)的,也希望大家可以向我提出一些建議。
源碼github地址:https://github.com/chaychan/PowerfulViewLibrary.git