重學Android-EditText的進階操作
theme: juejin highlight: a11y-dark
EditText的進階使用
EditText 是我們常用的輸入框控制元件,平常我們只是使用它輸入文字,這裡記錄一些它不太常見的操作和一些解決方案。
一、焦點的自動獲取
如果一個頁面內定義了EditText,那麼有可能我們進入此頁面的時候會自動彈起軟鍵盤,(分機型,有的會彈,有的不彈)。如果我們需要彈軟鍵盤,我們制定給 EditText 設定
xml
android:focusable="true"
android:focusableInTouchMode="true"
但是如果我們不想這個頁面進去就彈出軟鍵盤,我們可以給根佈局或者 EditText 的父佈局設定 focusable 。
二、游標和背景的控制
預設的 EditText 是帶下劃線和粗游標的,我們可以對它們進行簡單的修改 ```xml android:background="@null" //去掉了下劃線
android:textCursorDrawable="@null" //去掉游標的顏色 ```
自定義游標的顏色和寬度:
```xml
<size android:width="2dp" />
<solid android:color="#BDC7D8" />
```
使用自定義游標
xml
android:textCursorDrawable="@drawable/edittext_cursor"
三、限制小數點位數
我們可以通過監聽 EditText 的文字變化的方式來改變文字值,我們還能通過 DigitsKeyListener 的方式監聽文字的改變。
3.1 TextWatcher的方式
我們可以通過監聽 EditText 的文字變化,比如我們只想要小數點後面2位數,我們就監聽文字變化,點後面的2位數,如果多了就把他刪除掉。
```java public class MoneyTextWatcher implements TextWatcher { private EditText editText; private int digits = 2;
public MoneyTextWatcher(EditText et) {
editText = et;
}
public MoneyTextWatcher setDigits(int d) {
digits = d;
return this;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//刪除“.”後面超過2位後的資料
if (s.toString().contains(".")) {
if (s.length() - 1 - s.toString().indexOf(".") > digits) {
s = s.toString().subSequence(0,
s.toString().indexOf(".") + digits+1);
editText.setText(s);
editText.setSelection(s.length()); //游標移到最後
}
}
//如果"."在起始位置,則起始位置自動補0
if (s.toString().trim().substring(0).equals(".")) {
s = "0" + s;
editText.setText(s);
editText.setSelection(2);
}
//如果起始位置為0,且第二位跟的不是".",則無法後續輸入
if (s.toString().startsWith("0")
&& s.toString().trim().length() > 1) {
if (!s.toString().substring(1, 2).equals(".")) {
editText.setText(s.subSequence(0, 1));
editText.setSelection(1);
return;
}
}
}
@Override
public void afterTextChanged(Editable s) {
}
}
```
使用:
```java //預設兩位小數 mEditText.addTextChangedListener(new MoneyTextWatcher(mEditText1));
//手動設定其他位數,例如3 mEditText.addTextChangedListener(new MoneyTextWatcher(mEditText1).setDigits(3); ```
3.2 DigitsKeyListener的方式
```java public class ETMoneyValueFilter extends DigitsKeyListener {
public ETMoneyValueFilter(int d) {
super(false, true);
digits = d;
}
private int digits = 2; //預設顯示二位數的小數點
public ETMoneyValueFilter setDigits(int d) {
digits = d;
return this;
}
@Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
CharSequence out = super.filter(source, start, end, dest, dstart, dend);
if (out != null) {
source = out;
start = 0;
end = out.length();
}
int len = end - start;
if (len == 0) {
return source;
}
//以點開始的時候,自動在前面新增0
if (source.toString().equals(".") && dstart == 0) {
return "0.";
}
//如果起始位置為0,且第二位跟的不是".",則無法後續輸入
if (!source.toString().equals(".") && dest.toString().equals("0")) {
return "";
}
int dlen = dest.length();
for (int i = 0; i < dstart; i++) {
if (dest.charAt(i) == '.') {
return (dlen - (i + 1) + len > digits) ?
"" :
new SpannableStringBuilder(source, start, end);
}
}
for (int i = start; i < end; ++i) {
if (source.charAt(i) == '.') {
if ((dlen - dend) + (end - (i + 1)) > digits)
return "";
else
break;
}
}
return new SpannableStringBuilder(source, start, end);
}
} ```
其實是和 TextWatcher 類似的方式,那麼使用的時候我們這樣使用: ```java //預設兩位小數 mEditText.setFilters(new InputFilter[]{new MoneyValueFilter()});
//手動設定其他位數,例如3 mEditText.setFilters(new InputFilter[]{new MoneyValueFilter().setDigits(3)}); ```
在Kotlin程式碼中是這樣使用:
kotlin
et_input.filters = arrayOf(ETMoneyValueFilter().setDigits(3))
這樣就可以實現小數點後面二位數的控制,還順便加入了.的判斷,自動加0的操作。
四、EditText的Search操作
當此 EditText 軟鍵盤彈起的時候,右下角的確定變為搜尋,我們需要給 EditText 設定一個屬性:
xml
android:imeOptions="actionSearch"
然後給軟鍵盤設定一個監聽 ```java //點選軟鍵盤搜尋按鈕 etSearch.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
// 先隱藏鍵盤
((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE))
.hideSoftInputFromWindow(TransactionHistorySearchActivity.this.getCurrentFocus()
.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
if (isSearch){
isSearch = false;
if (!TextUtils.isEmpty(etSearch.getText().toString()))
searchHistory(etSearch.getText().toString());
}
}
return false;
}
});
```
這裡使用一個flag來判斷,是因為部分機型會回撥2次。所以為了統一效果,我們使用攔截判斷只調用一次。
當然Search的邏輯如果你使用 Kotlin + DataBinding 來實現,那麼就更簡單了。
```kotlin //執行搜尋 fun doSearch() { KeyboardUtils.hideSoftInput(mActivity) scrollTopRefresh() }
//搜尋的刪除
fun searchDel() {
mViewModel.mKeywordLiveData.value = ""
doSearch()
}
```
```xml
<ImageView
android:layout_width="@dimen/d_16dp"
android:layout_height="@dimen/d_16dp"
android:layout_marginLeft="@dimen/d_12dp"
android:src="@drawable/search_icon"
binding:clicks="@{click.doSearch}" />
<EditText
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/d_12dp"
android:layout_weight="1"
android:background="@color/transparent"
android:hint="大家都在搜"
android:imeOptions="actionSearch"
android:singleLine="true"
android:text="@={viewModel.mKeywordLiveData}"
android:textColor="@color/black"
android:textColorHint="@color/gray_99"
android:textSize="@dimen/d_14sp"
binding:onKeyEnter="@{click.doSearch}"
binding:typefaceMedium="@{true}" />
<ImageView
android:layout_width="@dimen/d_16dp"
android:layout_height="@dimen/d_16dp"
android:layout_marginRight="@dimen/d_10dp"
android:src="@drawable/search_delete"
android:visibility="gone"
binding:clicks="@{click.searchDel}"
binding:isVisibleGone="@{!TextUtils.isEmpty(viewModel.MKeywordLiveData)}" />
</LinearLayout>
```
主要的 Binding Adapter 方法為 onKeyEnter ,它實現了軟鍵盤的搜尋。
下面是自定義BindingAdapter的方法: ```kotlin var _viewClickFlag = false var _clickRunnable = Runnable { _viewClickFlag = false }
/* * Edit的確認按鍵事件 / @BindingAdapter("onKeyEnter") fun EditText.onKeyEnter(action: () -> Unit) { setOnKeyListener { _, keyCode, _ -> if (keyCode == KeyEvent.KEYCODE_ENTER) { KeyboardUtils.closeSoftKeyboard(this)
if (!_viewClickFlag) {
_viewClickFlag = true
action()
}
removeCallbacks(_clickRunnable)
postDelayed(_clickRunnable, 1000)
}
[email protected] false
}
} ```
和上面Java的實現方式類似,同樣的做了防抖的操作。為了部分機型連續呼叫多次的問題。
效果:
五、焦點與軟鍵盤的自由控制
上面說到的焦點,不自動彈出軟鍵盤,如果我想自由的控制焦點與軟鍵盤怎麼辦?
一個例子來說明,比如我們的需求,點選 EditText 的時候彈出彈框提示使用者注意事項,當點選確定或者取消之後再繼續輸入。分解步驟如下:
- 我們點選EditText不能彈出軟鍵盤
- 監聽焦點獲取之後彈出彈框
- 彈框完成之後我們需要手動的給EditText焦點
- 獲取焦點之後需要設定游標與軟鍵盤
程式碼邏輯如下: ```java mBankAccountEt.setShowSoftInputOnFocus(false); mBankAccountEt.setOnFocusChangeListener((v, hasFocus) -> { if (hasFocus && !isShowedBankAccountNotice) { showBankAccountNoticePopup(); } });
private void showBankAccountNoticePopup() {
BasePopupView mPopupView = new XPopup.Builder(mActivity)
.moveUpToKeyboard(false)
.hasShadowBg(true)
.asCustom(new BankNameNoticePopup(mActivity, () -> {
isShowedBankAccountNotice = true;
mBankAccountEt.setShowSoftInputOnFocus(true);
//需要把焦點設定回EditText
mBankAccountEt.setFocusable(true);
mBankAccountEt.setFocusableInTouchMode(true);
mBankAccountEt.requestFocus();
mBankAccountEt.setSelection(mBankAccountEt.getText().toString().length());
KeyboardUtils.showKeyboard(mBankAccountEt);
}));
if (mPopupView != null && !mPopupView.isShow()) {
mPopupView.show();
}
}
```
彈框就不給大家展示了,非常簡單的彈窗,定義使用的彈窗庫,邏輯都在完成的回撥中。
KeyboardUtils工具類,控制EditText的軟鍵盤展示與隱藏 ```java / * 顯示鍵盤 * / public static void showKeyboard(View view) { InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) { view.requestFocus(); imm.showSoftInput(view, 0); } }
/*
* 隱藏鍵盤
* */
public static void hideKeyboard(View view){
InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(),0);
}
}
```
效果:
六、RV + EditText複用的問題
不知道大家有沒有在RV中使用過 EditText ,Item中如果有 EditText 那麼在滾出螢幕之後 再拉回來可能剛才輸入的文字就消失了,或者換成不是剛才輸入的文字了,是因為快取複用,可能複用了別的Item上面的 EditText 控制元件。
有幾種解決方法如下:
方法一: 強制的停用Recyclerview的複用
java
helper.setIsRecyclable(false);
但是RV就無法快取與回收了,如果你的Item數量就是固定的並且不多,那麼使用這個方法是最好的。
方法二: 通過監聽焦點來新增或移除Edittext的TextChangedListener
```java @Override protected void convert(BaseViewHolder helper, EdittextInRecyclerViewOfBean item) { EditText editText = helper.getView(R.id.et); editText.setText(item.getNum() + "");
TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
//這裡處理資料
if (TextUtils.isEmpty(s.toString())) {
item.setNum(0);
} else {
item.setNum(Integer.parseInt(s.toString()));
}
}
};
editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus){
editText.addTextChangedListener(textWatcher);
}else {
editText.removeTextChangedListener(textWatcher);
}
}
});
}
```
方法三: 通過view的setTag()方法解決
```java @Override protected void convert(BaseViewHolder helper, EdittextInRecyclerViewOfBean item) { TextWatcher textWatcher = new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (TextUtils.isEmpty(s.toString())) {
item.setNum(0);
} else {
item.setNum(Integer.parseInt(s.toString()));
}
}
};
EditText editText = helper.getView(R.id.et);
//為了避免TextWatcher在呼叫settext()時被呼叫,提前將它移除
if (editText.getTag() instanceof TextWatcher) {
editText.removeTextChangedListener((TextWatcher) editText.getTag());
}
editText.setText(item.getNum() + "");
//重新新增上TextWatcher監聽
editText.addTextChangedListener(textWatcher);
//將TextWatcher繫結到EditText
editText.setTag(textWatcher);
}
```
方法四: 為每個EditText的繫結位置
```java
public class EditTextInRecyclerViewAdapter extends RecyclerView.Adapter {
private List
public void setData(List<EdittextInRecyclerViewOfBean> list) {
this.mList = list;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_edittext, parent, false);
return new ViewHolder(v, new ITextWatcher());
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
ViewHolder viewHolder = (ViewHolder) holder;
viewHolder.mITextWatcher.bindPosition(position);
viewHolder.mEditText.setText(mList.get(position).getNum()+"");
}
@Override
public int getItemCount() {
return mList.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
EditText mEditText;
ITextWatcher mITextWatcher;
private ViewHolder(View v, ITextWatcher watcher) {
super(v);
this.mEditText = v.findViewById(R.id.et);
this.mITextWatcher = watcher;
this.mEditText.addTextChangedListener(watcher);
}
}
class ITextWatcher implements TextWatcher {
private int position;
private void bindPosition(int position) {
this.position = position;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (TextUtils.isEmpty(s.toString())) {
mList.get(position).setNum(0);
} else {
mList.get(position).setNum(Integer.parseInt(s.toString()));
}
}
}
}
```
方法五: 構造方法中新增TextChanged ```java class PicViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var ivPic: ImageView = itemView.findViewById(R.id.ivPic)
var etScore: EditText = itemView.findViewById(R.id.etScore)
var tvTitle: TextView = itemView.findViewById(R.id.tvTitle)
var myTextWatcher: MyTextWatcher = MyTextWatcher()
init {
etScore.addTextChangedListener(myTextWatcher)
}
fun updateView(picItem: PicItem) {
myTextWatcher.picItem = picItem
ivPic.setImageResource(picItem.picResId)
tvTitle.text = picItem.title
if (picItem.score == null) {
etScore.hint = "請輸入分數"
etScore.setText("")
} else {
etScore.setText(picItem.score)
}
}
}
class MyTextWatcher: TextWatcher {
lateinit var picItem:PicItem
override fun afterTextChanged(s: Editable?) {
picItem?.apply {
score=s?.toString()
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
} ```
方法六: 讓產品改需求,不要使用EditText,或者我們乾脆使用TextView,然後點選Item彈出輸入框的彈框方式來實現。
後記
總結了很多 EditText 不常見的一些問題與解決方案,不知道大家有沒有遇到別的問題,或者一些 EditText 的騷操作,都可以評論區留言補充哦!
關於 EditText 的分享就到這裡了,後面如果有補充我會繼續更新!
完結.
我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿。
- 不就new個物件的事,為什麼要把簡單的問題複雜化?為什麼要使用Hilt依賴注入?看完就知道!
- Kotlin協程-協程的暫停與恢復 & suspendCancellableCoroutine的使用
- Kotlin協程-併發安全的幾種解決方案與效能對比
- Kotlin協程-協程的日常進階使用
- Kotlin協程-CoroutineScope協程作用域
- Kotlin協程-CoroutineContext協程上下文
- Kotlin協程-Coroutines的基本使用
- 重學Android-EditText的進階操作
- 一看就會 單Activity 多Fragment框架下的通訊問題
- Android開發小技巧-動態設定Drawable與Tint
- 開箱即用-Android設計模式實戰-攔截實現Log的列印與儲存
- Kotlin-高階函式進階之路
- 重學Android-Android的幾種動畫定義與使用
- Android開發小技巧-點9圖的使用
- ConstraintLayout真的就那麼強?拉出來比一比!
- 30 萌新程式設計師學習成長之路
- Android中協調滾動常用的佈局實現
- Android巢狀滾動與協調滾動的幾種實現方式(二)
- Android巢狀滾動與協調滾動的幾種實現方式(一)
- Android實現訊息匯流排的幾種方式,你都會嗎?