Mask an EditText with Phone Number Format NaN like in PhoneNumberUtils

后端 未结 8 1281
广开言路
广开言路 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: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()
        }
    }
    

提交回复
热议问题