Mask an EditText with Phone Number Format NaN like in PhoneNumberUtils
I want to make user inputted phone number in an editText to dynamically change format every time the user inputs a number. That is, when user inputs up to 4 digits, like 7144, the editText shows "714-4". I would like the editText to be dynamically 开发者_运维问答updated to format ###-###-#### whenever the user inputs a digit. how can this be done? also, I am handling more than one editTexts.
Easiest way to do this is to use the built in Android PhoneNumberFormattingTextWatcher.
So basically you get your EditText in code and set your text watcher like this...
EditText inputField = (EditText) findViewById(R.id.inputfield);
inputField.addTextChangedListener(new PhoneNumberFormattingTextWatcher());
Nice thing about using PhoneNumberFormattingTextWatcher is that it will format your number entry correctly based on your locale.
Above answer is right but it works with country specific. if anyone want such formatted phone number(###-###-####). Then use this:
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) {
}
});
Declare String lastChar = " "
in your activity.
Now add this line in xml of your edittext
android:inputType="phone"
That's all.
Edited: If you want your edittext lenght to limit 10 digits add line below also:
android:maxLength="12"
(It is 12 because "-" will take space two times)
Just add the following to EditText for Phone Number to get a formatted phone number(###-###-####)
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, "-");
}
}
}
});
My script, example taken from here 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;
}
}
};
Dynamic Mask for Android in Kotlin. This one is working fine and strictly fitting the phone number mask. You can provide any mask you whish.
EDIT1: I have a new version that locks event the unwanted chars typed by the user on the keyboard.
/**
* 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, "")
}
}
EDIT 2: Unit tests
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()
}
}
The above solutions do not take backspace into consideration so when you delete some numbers after typing, the format tends to mess up. Below code corrects this issue.
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) { }
});
This code allow you enter phone number with mask ### - ### - #### (without spaces) and also here is fixed the issue with deletion of phone digits:
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) {}
});
Layout:
<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"/>
Here is my solution
How run in Activity/Fragment (f.e in onViewCreated):
//field in class
private val exampleIdValidator by lazy { ExampleIdWatcher(exampleIdField.editText!!) }
exampleIdField.editText?.addTextChangedListener(exampleIdValidator)
Validatior class:
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 = " "
}
}
Layout:
<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" />
Result is:
XX XXX XXX XXX
精彩评论