【问题标题】:Mask an EditText with Phone Number Format NaN like in PhoneNumberUtils使用电话号码格式 NaN 屏蔽 EditText,如 PhoneNumberUtils
【发布时间】:2011-05-08 08:33:06
【问题描述】:

我想让用户在 editText 中输入电话号码,以便在用户每次输入号码时动态更改格式。也就是说,当用户输入最多 4 位数字(如 7144)时,editText 显示“714-4”。 我希望在用户输入数字时动态更新editText以格式化###-###-####。如何才能做到这一点?另外,我处理的编辑文本不止一个。

【问题讨论】:

    标签: android formatting android-edittext masking


    【解决方案1】:

    在 Kotlin 中用于 Android 的动态掩码。这个工作正常,严格符合电话号码掩码。你可以提供任何你想要的面具。

    EDIT1:我有一个新版本,可以锁定用户在键盘上键入的不需要的字符。

    /**
     * Text watcher allowing strictly a MASK with '#' (example: (###) ###-####
     */
    class NumberTextWatcher(private var mask: String) : TextWatcher {
        companion object {
            const val MASK_CHAR = '#'
        }
    
        // simple mutex
        private var isCursorRunning = false
        private var isDeleting = false
    
        override fun afterTextChanged(s: Editable?) {
            if (isCursorRunning || isDeleting) {
                return
            }
            isCursorRunning = true
    
            s?.let {
                val onlyDigits = removeMask(it.toString())
                it.clear()
                it.append(applyMask(mask, onlyDigits))
            }
    
            isCursorRunning = false
        }
    
        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
            isDeleting = count > after
        }
    
        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
    
        private fun applyMask(mask: String, onlyDigits: String): String {
            val maskPlaceholderCharCount = mask.count { it == MASK_CHAR }
            var maskCurrentCharIndex = 0
            var output = ""
    
            onlyDigits.take(min(maskPlaceholderCharCount, onlyDigits.length)).forEach { c ->
                for (i in maskCurrentCharIndex until mask.length) {
                    if (mask[i] == MASK_CHAR) {
                        output += c
                        maskCurrentCharIndex += 1
                        break
                    } else {
                        output += mask[i]
                        maskCurrentCharIndex = i + 1
                    }
                }
            }
            return output
        }
    
        private fun removeMask(value: String): String {
            // extract all the digits from the string
            return Regex("\\D+").replace(value, "")
        }
    }
    

    编辑 2: 单元测试

    class NumberTextWatcherTest {
    
        @Test
        fun phone_number_test() {
            val phoneNumberMask = "(###) ###-####"
            val phoneNumberTextWatcher = NumberTextWatcher(phoneNumberMask)
    
            val input = StringBuilder()
            val expectedResult = "(012) 345-6789"
            var result = ""
    
            // mimic typing 10 digits
            for (i in 0 until 10) {
                input.append(i)
                result = mimicTextInput(phoneNumberTextWatcher, result, i.toString()) ?: ""
            }
    
            Assert.assertEquals(input.toString(), "0123456789")
            Assert.assertEquals(result, expectedResult)
        }
    
        @Test
        fun credit_card_test() {
            val creditCardNumberMask = "#### #### #### ####"
            val creditCardNumberTextWatcher = NumberTextWatcher(creditCardNumberMask)
    
            val input = StringBuilder()
            val expectedResult = "0123 4567 8901 2345"
            var result = ""
    
            // mimic typing 16 digits
            for (i in 0 until 16) {
                val value = i % 10
                input.append(value)
                result = mimicTextInput(creditCardNumberTextWatcher, result, value.toString()) ?: ""
            }
    
            Assert.assertEquals(input.toString(), "0123456789012345")
            Assert.assertEquals(result, expectedResult)
        }
    
        @Test
        fun date_test() {
            val dateMask = "####/##/##"
            val dateTextWatcher = NumberTextWatcher(dateMask)
    
            val input = "20200504"
            val expectedResult = "2020/05/04"
            val initialInputValue = ""
    
            val result = mimicTextInput(dateTextWatcher, initialInputValue, input)
    
            Assert.assertEquals(result, expectedResult)
        }
    
        @Test
        fun credit_card_expiration_date_test() {
            val creditCardExpirationDateMask = "##/##"
            val creditCardExpirationDateTextWatcher = NumberTextWatcher(creditCardExpirationDateMask)
    
            val input = "1121"
            val expectedResult = "11/21"
            val initialInputValue = ""
    
            val result = mimicTextInput(creditCardExpirationDateTextWatcher, initialInputValue, input)
    
            Assert.assertEquals(result, expectedResult)
        }
    
        private fun mimicTextInput(textWatcher: TextWatcher, initialInputValue: String, input: String): String? {
            textWatcher.beforeTextChanged(initialInputValue, initialInputValue.length, initialInputValue.length, input.length + initialInputValue.length)
            val newText = initialInputValue + input
    
            textWatcher.onTextChanged(newText, 1, newText.length - 1, 1)
            val editable: Editable = SpannableStringBuilder(newText)
    
            textWatcher.afterTextChanged(editable)
            return editable.toString()
        }
    }
    

    【讨论】:

      【解决方案2】:

      这是我的解决方案

      如何在 Activity/Fragment 中运行(f.e in onViewCreated):

      //field in class
      private val exampleIdValidator by lazy { ExampleIdWatcher(exampleIdField.editText!!) }
      
      exampleIdField.editText?.addTextChangedListener(exampleIdValidator)
      

      验证器类:

          import android.text.Editable
          import android.text.TextWatcher
          import android.widget.EditText
      
          class ExampleIdWatcher(exampleIdInput: EditText) : TextWatcher {
      
              private var exampleIdInput: EditText = exampleIdInput
      
              override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
              }
      
              override fun onTextChanged(userInput: CharSequence?, start: Int, before: Int, count: Int) {
                  if (userInput!!.isNotEmpty() && !areSpacesCorrect(userInput)) {
      
                      val stringTextWithoutWhiteSpaces: String = userInput.toString().replace(" ", "")
      
                      val textSB: StringBuilder = StringBuilder(stringTextWithoutWhiteSpaces)
      
                      when {
                          textSB.length > 8 -> {
                              setSpacesAndCursorPosition(textSB, 2, 6, 10)
                          }
                          textSB.length > 5 -> {
                              setSpacesAndCursorPosition(textSB, 2, 6)
                          }
                          textSB.length > 2 -> {
                              setSpacesAndCursorPosition(textSB, 2)
                          }
                      }
                  }
              }
      
              override fun afterTextChanged(s: Editable?) {
              }
      
              private fun setSpacesAndCursorPosition(textSB: StringBuilder, vararg ts: Int) {
                  for (t in ts) // ts is an Array
                      textSB.insert(t, SPACE_CHAR)
                  val currentCursorPosition = getCursorPosition(exampleIdInput.selectionStart)
                  exampleIdInput.setText(textSB.toString())
                  exampleIdInput.setSelection(currentCursorPosition)
              }
      
              private fun getCursorPosition(currentCursorPosition: Int): Int {
                  return if (EXAMPLE_ID_SPACE_CHAR_CURSOR_POSITIONS.contains(currentCursorPosition)) {
                      currentCursorPosition + 1
                  } else {
                      currentCursorPosition
                  }
              }
      
              private fun areSpacesCorrect(userInput: CharSequence?): Boolean {
                  EXAMPLE_ID_SPACE_CHAR_INDEXES.forEach {
                      if (userInput!!.length > it && userInput[it].toString() != SPACE_CHAR) {
                          return false
                      }
                  }
                  return true
              }
      
              companion object {
                  private val EXAMPLE_ID_SPACE_CHAR_INDEXES: List<Int> = listOf(2, 6, 10)
                  private val EXAMPLE_ID_SPACE_CHAR_CURSOR_POSITIONS: List<Int> = EXAMPLE_ID_SPACE_CHAR_INDEXES.map { it + 1 }
                  private const val SPACE_CHAR: String = " "
              }
          }
      

      布局:

          <com.google.android.material.textfield.TextInputEditText
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:digits=" 0123456789"
              android:inputType="numberPassword"
              android:maxLength="14"
              tools:text="Example text" />
      

      结果是:

      XX XXX XXX XXX
      

      【讨论】:

      • 虽然这段代码 sn-p 可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您正在为将来的读者回答问题,而这些人可能不知道您的代码建议的原因。也请尽量不要用解释性的 cmets 挤满你的代码,这会降低代码和解释的可读性!
      【解决方案3】:

      此代码允许您输入带有掩码 ### - ### - ####(不带空格)的电话号码,并且这里还修复了删除电话数字的问题:

      editText.addTextChangedListener(new TextWatcher() {
                  final static String DELIMITER = "-";
                  String lastChar;
      
                  @Override
                  public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                      int digits = editText.getText().toString().length();
                      if (digits > 1)
                          lastChar = editText.getText().toString().substring(digits-1);
                  }
      
                  @Override
                  public void onTextChanged(CharSequence s, int start, int before, int count) {
                      int digits = editText.getText().length();
                      // prevent input dash by user
                      if (digits > 0 && digits != 4 && digits != 8) {
                          CharSequence last = s.subSequence(digits - 1, digits);
                          if (last.toString().equals(DELIMITER))
                              editText.getText().delete(digits - 1, digits);
                      }
                      // inset and remove dash
                      if (digits == 3 || digits == 7) {
                          if (!lastChar.equals(DELIMITER))
                              editText.append("-"); // insert a dash
                          else
                              editText.getText().delete(digits -1, digits); // delete last digit with a dash
                      }
                      dataModel.setPhone(s.toString());
                  }
      
                  @Override
                  public void afterTextChanged(Editable s) {}
              });
      

      布局:

      <EditText
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:imeOptions="actionDone"
                  android:textAlignment="textStart"
                  android:inputType="number"
                  android:digits="-0123456789"
                  android:lines="1"
                  android:maxLength="12"/>
      

      【讨论】:

      • 是的,这有效并且还处理了背压场景
      【解决方案4】:

      上述解决方案没有考虑退格,因此当您在键入后删除一些数字时,格式往往会混乱。下面的代码纠正了这个问题。

      phoneNumberEditText.addTextChangedListener(new TextWatcher() {
      
              int beforeLength;
      
              @Override
              public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                  beforeLength = phoneNumberEditText.length();
              }
      
              @Override
              public void onTextChanged(CharSequence s, int start, int before, int count) {
                  int digits = phoneNumberEditText.getText().toString().length();
                  if (beforeLength < digits && (digits == 3 || digits == 7)) {
                      phoneNumberEditText.append("-");
                  }
              }
      
              @Override
              public void afterTextChanged(Editable s) { }
          });
      

      【讨论】:

        【解决方案5】:

        最简单的方法是使用内置的 Android PhoneNumberFormattingTextWatcher

        所以基本上你在代码中得到你的 EditText 并像这样设置你的文本观察器......

        EditText inputField = (EditText) findViewById(R.id.inputfield);
        inputField.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
        

        使用 PhoneNumberFormattingTextWatcher 的好处是它会根据您的语言环境正确格式化您的号码输入。

        【讨论】:

        • 如果您需要自定义掩码,您会发现此答案很有用:stackoverflow.com/a/34907607/1013929
        • editTextPhone.addTextChangedListener(PhoneNumberFormattingTextWatcher()) Kotlin
        【解决方案6】:

        我的脚本,示例取自这里description here


        <android.support.design.widget.TextInputLayout
            android:id="@+id/numphone_layout"
            app:hintTextAppearance="@style/MyHintText"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
        
            android:layout_marginTop="8dp">
        
            <android.support.design.widget.TextInputEditText
                android:id="@+id/edit_text_numphone"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:theme="@style/MyEditText"
                android:digits="+() 1234567890-"
                android:hint="@string/hint_numphone"
                android:inputType="phone"
                android:maxLength="17"
                android:textSize="14sp" />
        </android.support.design.widget.TextInputLayout>
        

         @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
         TextInputEditText phone = (TextInputEditText) findViewById(R.id.edit_text_numphone);
         //Add to mask
            phone.addTextChangedListener(textWatcher);
        }
        
        
           TextWatcher textWatcher = new TextWatcher() {
            private boolean mFormatting; // this is a flag which prevents the  stack overflow.
            private int mAfter;
        
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // nothing to do here..
            }
        
            //called before the text is changed...
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                //nothing to do here...
                mAfter  =   after; // flag to detect backspace..
        
            }
        
            @Override
            public void afterTextChanged(Editable s) {
                // Make sure to ignore calls to afterTextChanged caused by the work done below
                if (!mFormatting) {
                    mFormatting = true;
                    // using US or RU formatting...
                    if(mAfter!=0) // in case back space ain't clicked...
                    {
                        String num =s.toString();
                        String data = PhoneNumberUtils.formatNumber(num, "RU");
                        if(data!=null)
                        {
                            s.clear();
                            s.append(data);
                            Log.i("Number", data);//8 (999) 123-45-67 or +7 999 123-45-67
                        }
        
                    }
                    mFormatting = false;
                }
            }
        };
        

        【讨论】:

        • 这是唯一有效的最佳解决方案
        【解决方案7】:

        只需将以下内容添加到电话号码的 EditText 即可获得格式化的电话号码(###-###-####)

        Phone.addTextChangedListener(new TextWatcher() {
        
                int length_before = 0;
        
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    length_before = s.length();
                }
        
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
        
                }
        
                @Override
                public void afterTextChanged(Editable s) {
                    if (length_before < s.length()) {
                        if (s.length() == 3 || s.length() == 7)
                            s.append("-");
                        if (s.length() > 3) {
                            if (Character.isDigit(s.charAt(3)))
                                s.insert(3, "-");
                        }
                        if (s.length() > 7) {
                            if (Character.isDigit(s.charAt(7)))
                                s.insert(7, "-");
                        }
                    }
                }
            });
        

        【讨论】:

          【解决方案8】:

          以上答案是正确的,但它适用于特定国家/地区。如果有人想要这种格式的电话号码(###-###-####)。然后使用这个:

          etPhoneNumber.addTextChangedListener(new TextWatcher() {
                          @Override
                          public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                              int digits = etPhoneNumber.getText().toString().length();
                              if (digits > 1)
                                  lastChar = etPhoneNumber.getText().toString().substring(digits-1);
                          }
          
                          @Override
                          public void onTextChanged(CharSequence s, int start, int before, int count) {
                              int digits = etPhoneNumber.getText().toString().length();
                              Log.d("LENGTH",""+digits);
                              if (!lastChar.equals("-")) {
                                  if (digits == 3 || digits == 7) {
                                      etPhoneNumber.append("-");
                                  }
                              }
                          }
          
                          @Override
                          public void afterTextChanged(Editable s) {
          
                          }
                      });
          

          在你的活动中声明String lastChar = " "

          现在在你的edittext的xml中添加这一行

          android:inputType="phone"
          

          就是这样。

          已编辑:如果您希望编辑文本长度限制为 10 位数字,请在下面添加行:

          android:maxLength="12"
          

          (是12,因为“-”会占用两次空间)

          【讨论】:

          • 这适用于 XXX-XXX-XXXX 格式的电话号码。只需复制粘贴即可。
          猜你喜欢
          • 2011-08-31
          • 1970-01-01
          • 2018-05-04
          • 2018-05-07
          • 2012-07-24
          • 2022-01-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多