Mask an EditText with Phone Number Format NaN like in PhoneNumberUtils

后端 未结 8 1262
广开言路
广开言路 2020-12-05 05:08

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 714

相关标签:
8条回答
  • 2020-12-05 05:18

    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"/>
    
    0 讨论(0)
  • 2020-12-05 05:20

    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.

    0 讨论(0)
  • 2020-12-05 05:20

    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)

    0 讨论(0)
  • 2020-12-05 05:27

    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, "-");
                    }
                }
            }
        });
    
    0 讨论(0)
  • 2020-12-05 05:27

    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
    
    0 讨论(0)
  • 2020-12-05 05:30

    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()
        }
    }
    
    0 讨论(0)
提交回复
热议问题