I have a solution that allows the user to enter both the dot and the comma (if available on the keyboard), but only displays the locale default separator. In addition it will not allow the user to enter more than 1 separator. No issues with references to EditText
or infinite loops. It is a combination of several answers in this thread suited to my needs.
As with the accepted answer, configure the EditText
accordingly:
android:inputType="numberDecimal"
android:digits="0123456789.,"
Then set a custom TextWatcher on the EditText:
myEditText.addTextChangedListener(FlexibleDecimalSeparatorTextWatcher())
And include the custom TextWatcher:
import android.text.Editable
import android.text.SpannableStringBuilder
import android.text.TextWatcher
import android.widget.EditText
import java.text.DecimalFormatSymbols
/**
* The [FlexibleDecimalSeparatorTextWatcher] allows the user to input both the comma (,) and dot (.) as a decimal separator,
* and will then automatically convert each entered separator into the locale default separator.
* If the user were to enter multiple separators - every separator but the first will be removed.
*
* To provide comma and dot support, set the [EditText] inputType to 'numberDecimal' and its digits to '0123456789.,'.
*/
class FlexibleDecimalSeparatorTextWatcher : TextWatcher {
companion object {
private val DECIMAL_SEPARATORS = listOf('.', ',')
private val LOCALE_DEFAULT_DECIMAL_SEPARATOR = DecimalFormatSymbols.getInstance().decimalSeparator
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
if (s != null) {
val textWithConvertedSeparators = convertSeparatorsToLocaleDefault(s.toString())
val textWithoutMultipleSeparators = removeAdditionalSeparators(textWithConvertedSeparators)
// Make the change if required. This only triggers one additional afterTextChanged call if there were changes.
if(s.toString() != textWithoutMultipleSeparators) {
s.replace(0, s.length, SpannableStringBuilder(textWithoutMultipleSeparators))
}
}
}
/**
* This function converts all entered separators (in [DECIMAL_SEPARATORS]) to the [LOCALE_DEFAULT_DECIMAL_SEPARATOR].
*/
private fun convertSeparatorsToLocaleDefault(original: String): String {
var result = original
DECIMAL_SEPARATORS.forEach { separator ->
if (separator != LOCALE_DEFAULT_DECIMAL_SEPARATOR && result.contains(separator)) {
result = result.replace(separator, LOCALE_DEFAULT_DECIMAL_SEPARATOR)
}
}
return result
}
/**
* Strip out all separators but the first.
* In this function we assume all separators are already converted to the locale default.
*/
private fun removeAdditionalSeparators(original: String): String {
var result = original
var separatorCount = result.count { c -> c == LOCALE_DEFAULT_DECIMAL_SEPARATOR }
if(separatorCount > 1) {
// We will reverse the text so we can keep stripping the last (first in reverse) separator off.
var textReversed = result.reversed()
val separatorRegex = Regex.fromLiteral(LOCALE_DEFAULT_DECIMAL_SEPARATOR.toString())
while (separatorCount > 1) {
textReversed = textReversed.replaceFirst(separatorRegex, "")
separatorCount--
}
// And finally we reverse it back to the original order.
result = textReversed.reversed()
}
return result
}
}