使用电话号码格式 NaN 屏蔽 EditText,如 PhoneNumberUtils
Posted
技术标签:
【中文标题】使用电话号码格式 NaN 屏蔽 EditText,如 PhoneNumberUtils【英文标题】:Mask an EditText with Phone Number Format NaN like in PhoneNumberUtils 【发布时间】:2011-05-08 08:33:06 【问题描述】:我想让用户在 editText 中输入电话号码,以便在用户每次输入号码时动态更改格式。也就是说,当用户输入最多 4 位数字(如 7144)时,editText 显示“714-4”。 我希望在用户输入数字时动态更新editText以格式化###-###-####。如何才能做到这一点?另外,我处理的编辑文本不止一个。
【问题讨论】:
【参考方案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_
android:layout_
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_
android:layout_
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 的好处是它会根据您的语言环境正确格式化您的号码输入。
【讨论】:
如果您需要自定义掩码,您会发现此答案很有用:***.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_
android:layout_
android:layout_marginTop="8dp">
<android.support.design.widget.TextInputEditText
android:id="@+id/edit_text_numphone"
android:layout_
android:layout_
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 格式的电话号码。只需复制粘贴即可。以上是关于使用电话号码格式 NaN 屏蔽 EditText,如 PhoneNumberUtils的主要内容,如果未能解决你的问题,请参考以下文章
Android 电话号码格式不适用于键盘和 EditText