【Android】設定EditText為僅輸入數字且最多隻能有兩位數字
需求很簡單,就是要設定一個EditText僅能輸入數字且輸入的數字中小數部分最多可以有兩位。
第一步,很簡單,在XML檔案中,將EditText的inputType設定成NumberDecimal,多餘的屬性我就不寫出來,只寫出主要的部分:
<EditText
。。。
android:inputType="numberDecimal"
。。。
/>
第二部,程式碼中修改EditText 的addTextChangedListener 方法,同樣的先上程式碼,再來解釋:
EditText.addTextChangedListener(new TextWatcher() { // Tips: // 1. onTextChanged和beforTextChanged傳入的引數s其實是當前EditText的文字內容,而不是當前輸入的內容 // 2. 如果在任意一個方法中呼叫了設定當前EditText文字的方法,setText(),實際都觸發了一遍這3個函式, // 所以要有判斷條件,在if體內去setText,而且就需要手動設定游標的位置,不然每次游標都會到最開始的位置 // 3. onTextChanged中,before=0: 增加;before=1: 點選刪除按鍵 private int count_decimal_points_ = 0; // 標識當前是不是已經有小數點了 private int selection_start_; // 監聽游標的位置 private StringBuffer str_buf_; // 快取當前的string,用以修改內容 @Override public void onTextChanged(CharSequence s, int start, int before, int count) { str_buf_ = new StringBuffer(s.toString().trim()); // 先判斷輸入的第一位不能是小數點 if (before == 0 && s.length() == 1 && s.charAt(start) == '.') { recharge_money.setText(""); } else if (before == 0 && count_decimal_points_ == 1) { // 在判斷如果當前是增加,並且已經有小數點了,就要判斷輸入是否合法;如果是減少不做任何判斷 // 注意在if語句中都是在else體內呼叫了設定游標監聽位的方法,因為在呼叫setText之後會出現巢狀的情況 // 非合法的輸入包括: 1. 輸入的依舊是小數點,2.小數點後位數已經達到兩位了 if (s.charAt(start) == '.' || (start - str_buf_.indexOf(".") > 2) ) { str_buf_.deleteCharAt(start); recharge_money.setText(str_buf_); } else { selection_start_ = str_buf_.length(); // 設定游標的位置為結尾 } } else { selection_start_ = str_buf_.length(); // 設定游標的位置為結尾 } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { if (s.toString().contains(".")) { count_decimal_points_ = 1; } else { count_decimal_points_ = 0; // 因為可能存在如果是刪除的話,把小數點刪除的情況 } } @Override public void afterTextChanged(Editable s) { // 重置游標位置 recharge_money.setSelection(selection_start_); if (s != null) { try { // TODO Your things } catch (NumberFormatException e) { e.printStackTrace(); } } } });
其實我們看到在listener中是new 了一個TextWatcher類,在TextWatcher類中,其實有3個方法,beforeTextChanged、onTextChanged和afterTextChanged。 從它們的名字中我們也不難看出分別是當text發生變化的時候,要做的處理。
在類中,我們聲明瞭3個變數,變數的作用我在註釋裡面也添加了,這裡就不囉嗦了。注意的是之所以用StringBuffer來代替String是因為StringBuffer的效率更高,至於為什麼,自行Google。
看了一下我的程式碼,覺得我該說的東西在註釋裡面都已經寫出來了。這裡需要特別注意的是,如果在你的類中,你呼叫了EditText的setText方法,那麼就會立即觸發TextWatch類,所以,如果你不加if條件判斷,而是直接setText,那麼就會出現死迴圈最終記憶體洩露而崩潰。所以一定要注意,比如你發現使用者的輸入不合法,要把剛輸入的內容刪除掉,重新set一下當前EditText的內容,那麼一定是在if迴圈語句中setText。
還有一點也是和這個相關的,就是我開始的時候,在onTextChanged方法中,把設定游標位置的語句放在了if迴圈體之外,因為我覺得每次不管你是不是滿足條件,都要執行一遍重置游標的操作,就出現了bug,後來是打log的時候才發現了,這裡我把程式碼中的log內容省去了。
具體情況我來給大家解釋一遍,我們先來看一下我最初的程式碼:
<pre name="code" class="java">public void onTextChanged(CharSequence s, int start, int before, int count) { str_buf_ = new StringBuffer(s.toString().trim()); // 先判斷輸入的第一位不能是小數點 if (before == 0 && s.length() == 1 && s.charAt(start) == '.') { LogUtil.logMsg("輸入錯誤","第一位不能是小數點"); recharge_money.setText(""); } else if (before == 0 && count_decimal_points_ == 1) { // 在判斷如果當前是增加,並且已經有小數點了,就要判斷輸入是否合法;如果是減少不做任何判斷 // 注意在if語句中都是在else體內呼叫了設定游標監聽位的方法,因為在呼叫setText之後會出現巢狀的情況 // 非合法的輸入包括: 1. 輸入的依舊是小數點,2.小數點後位數已經達到兩位了 if (s.charAt(start) == '.' || (start - str_buf_.indexOf(".") > 2) ) { str_buf_.deleteCharAt(start); EditText.setText(str_buf_); } } selection_start_ = str_buf_.length(); // 設定游標的位置為結尾 }
重點就在最後一行,我在任何時候都會設定游標的位置。我們來看bug是如何產生的:
加入這時候已經有一串合法的數字123.45寫在了EditText中,這個時候游標的位置是6,假設這個時候我們又輸入了一個數字6,那麼在TextWatch類中,selection_start_實際上變成了7,因為在這個方法中,CharSequence s,也就是str_buf_的長度是7,也就是123.456,然後我們發現已經有了兩位小數了,手動設定EditText的setText方法,把它改成了123.45,前面我們說過了,這裡會有一個巢狀呼叫,相當於遞迴了一下,注意我們的程式在afterTextChanged中要重置游標位置的,bug就出在這裡。
我們來畫一下流程吧:
123.456 onTextChanged --> 123.45 onTextChanged --> 123.45 afterTextChanged --> 123.456 afterTextChanged
我省去了beforeTextChanged的過程,但是可以看到,我們在迴圈體的外面是要呼叫123.456的afterTextChanged方法的,這個方法裡面的selection_start_也就是游標位置是7,但是其實,這個時候text的內容已經變成了123.45,那這個text只有6位,你要把游標設定在7,那一定會崩潰的。
所以,因為有巢狀的關係,我們一定要謹慎地處理每一步。