原文地址:讓你的 EditText 全部清除
項目地址(歡迎 Star):ClearEditText
在輸入文本的時候,通常當(dāng)前輸入的地方的末尾會有一個 'x' 來結(jié)束,它的作用是,如果我們想要清空這一整行輸入的時候,點一下 'x' 就可以了。它的存在,還是很有必要的。
然后,Android UI 組件并沒有提供這樣的功能,如果 Android 用戶在輸入了一段很長的文本的時候,發(fā)現(xiàn)他完全輸錯了,這時候想要刪除整行內(nèi)容的話,他必須一直按刪除鍵,或者長按選中整段文字,然后刪除。所以說,其實,這樣一個簡單 'x' 的存在是非常又必要的。
對于這個 'x' 我們有什么要求呢:
- 'x' 應(yīng)該只在我們編輯這一項文本的時候并且我們獲得了焦點的情況下才顯示
- 'x' 應(yīng)該顯示在這項文本內(nèi)部,即它應(yīng)該顯示在 EditText 里
- 按下 'x' 應(yīng)該是清除全部內(nèi)容
- 'x' 的顏色應(yīng)該是與編輯文本的主題色是一致的
這些要求意味著我們需要自定義 EditText。對于第一個要求,我們需要一個 TextWatcher,這樣的話我們就可以看到內(nèi)容字段發(fā)生的改變,還有我們需要實現(xiàn) onFocusChangeListener。第二個要求,我們可以實現(xiàn)一個 'x' 作為一個 compound drawable,因為這里沒有 onClick 事件去監(jiān)聽 compound drawable,我們需要使用 OnTouch 來監(jiān)聽,這樣第三個要求也實現(xiàn)了。對于第四個要求,我認(rèn)為 'x' 的顏色應(yīng)該是和 hint text 的 color 是一樣的。這樣才會有“想要就要”的感覺,而不是“我要我要”的感覺。
構(gòu)建我們的 EditText
創(chuàng)建新的 class,繼承自 AppCompatEditText 而非 EditText,以確保我們在所有的設(shè)備上都有和 Android5.0 一樣的效果。
public class ClearEditText extends AppCompatEditText implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {
創(chuàng)建構(gòu)造函數(shù)和進行初始化:
public ClearEditText(final Context context) {
super(context);
init(context);
}
public ClearEditText(final Context context, final AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ClearEditText(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
在 init 方法中,定義一個清除的圖標(biāo),因為我們用了 support library,所以可以使用這個圖標(biāo):abc_ic_clear_mtrl_alpha。但是因為它是白色的,所以我們使用著色(tint)的辦法讓它在 Android5.0 以前的設(shè)備上也能工作。用 DrawableCompat 類來完成我們的工作:
private void init(final Context context) {
final Drawable drawable = ContextCompat.getDrawable(context, R.drawable.abc_ic_clear_mtrl_alpha);
final Drawable wrappedDrawable = DrawableCompat.wrap(drawable); //Wrap the drawable so that it can be tinted pre Lollipop
DrawableCompat.setTint(wrappedDrawable, getCurrentHintTextColor());
mClearTextIcon = wrappedDrawable;
mClearTextIcon.setBounds(0, 0, mClearTextIcon.getIntrinsicHeight(), mClearTextIcon.getIntrinsicHeight());
setClearIconVisible(false);
super.setOnTouchListener(this);
super.setOnFocusChangeListener(this);
addTextChangedListener(this);
}
setClearIconVisible 來處理 EditText 的清除圖標(biāo)是否是顯示。我們通過焦點的變化和文本的觀察(text watchers)來決定顯示與否。
private void setClearIconVisible(final boolean visible) {
mClearTextIcon.setVisible(visible, false);
final Drawable[] compoundDrawables = getCompoundDrawables();
setCompoundDrawables(
compoundDrawables[0],
compoundDrawables[1],
visible ? mClearTextIcon : null,
compoundDrawables[3]);
}
這當(dāng)然意味著使用這樣的控制你就不應(yīng)該自己去設(shè)置 right 的 compound drawable,因為它總是會被重寫為 'x' 或者 null,當(dāng)文本改變或者焦點變化的時候。
你可以使用 TextInputLayout 來包裹 EditText。這是非常棒的。
監(jiān)聽其他的控制
細(xì)心的你肯定已經(jīng)注意到了,在 init 方法中我調(diào)用了父類的 Touch,和 Focus 的監(jiān)聽事件。這么做是因為,我想重寫標(biāo)準(zhǔn)的設(shè)置器,這樣我們就能第一時間捕獲這些監(jiān)聽。這樣就能應(yīng)用到我們的邏輯里,或者在邏輯里去設(shè)置監(jiān)聽。通過這種方式,如果我們用 TextInputLayout 包裹住了 EditText, 然后有 focus 監(jiān)聽。這樣的話這些設(shè)置在 EditText 也仍然會被 fired。我們不需要 text watchers,因為你已經(jīng)有了多個的 multiple text watchers 在編輯文本上。
@Override
public void setOnFocusChangeListener(final OnFocusChangeListener onFocusChangeListener) {
mOnFocusChangeListener = onFocusChangeListener;
}
@Override
public void setOnTouchListener(final OnTouchListener onTouchListener) {
mOnTouchListener = onTouchListener;
}
為這個新類定義一些字段:
private Drawable mClearTextIcon;
private OnFocusChangeListener mOnFocusChangeListener;
private OnTouchListener mOnTouchListener;
最終
實現(xiàn)3個監(jiān)聽,首先是 focus:
@Override
public void onFocusChange(final View view, final boolean hasFocus) {
if (hasFocus) {
setClearIconVisible(getText().length() > 0);
} else {
setClearIconVisible(false);
}
if (mOnFocusChangeListener != null) {
mOnFocusChangeListener.onFocusChange(view, hasFocus);
}
}
這樣當(dāng) focus 改變的時候,如果 editText 是獲得焦點的,并且文本內(nèi)容不為空,我們就顯示 清除的 icon,否則,我們就設(shè)置為 null(即不顯示 icon),在這兩種情況下,我們要對其他所有的 focus 改變進行監(jiān)聽,來確保它的邏輯是正常進行的。
onTouch 事件:
@Override
public boolean onTouch(final View view, final MotionEvent motionEvent) {
final int x = (int) motionEvent.getX();
if (mClearTextIcon.isVisible() && x > getWidth() - getPaddingRight() - mClearTextIcon.getIntrinsicWidth()) {
if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
setText("");
}
return true;
}
return mOnTouchListener != null && mOnTouchListener.onTouch(view, motionEvent);
}
我們先檢查清除的 icon 是否是顯示的,然后當(dāng)我們點擊右側(cè)區(qū)域,我們就應(yīng)該始終消耗這個事件,我們不希望其他的 touch 監(jiān)聽獲取到。最后,如果用戶的手指是從 'x' 所在的區(qū)域抬起來的。我們就要去清除文本。如果 'x' 是不顯示的或者 onTouch 事件不是在 right drawable 區(qū)域內(nèi)的,我們就應(yīng)該處理其他的對應(yīng)的 touch 監(jiān)聽。
最后是簡單的 TextWatcher:
@Override
public final void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
if (isFocused()) {
setClearIconVisible(s.length() > 0);
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
這樣,我們就擁有了帶有 'x' 可清除的全功能的的 EditText。你可以在你的布局中替換常規(guī)的 EditText 了。如果你是使用 AutoCompleteTextView ,也是可以修改的,只要該類名為如下就行了:
public class ClearableAutoCompleteTextView extends AppCompatAutoCompleteTextView implements View.OnTouchListener, View.OnFocusChangeListener, TextWatcher {