Please i need help to optimize the input filter on my customEditText
Requirement:
min float = 0.001
max float = 1000.000
decimal number = 3
I want to avoid 0 from the user set but he can enter something like 0.01 , 0.001 , 1.555 and 1000.000
This is what i've tested so far:
I used this function to set the min and max
class EditTextInputFilter(min: Float, max: Float) : InputFilter {
private val min: Float = min.coerceAtMost(max)
private val max: Float = min.coerceAtLeast(max)
override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
try {
val input = (spanned.toString() source.toString()).toFloatOrZero()
if (isInRange(min, max, input)) {
return null
}
} catch (nfe: NumberFormatException) {
Logger.error(nfe.localizedMessage!!)
}
return ""
}
private fun isInRange(min: Float, max: Float, value: Float): Boolean {
return value in min..max
}
}
I used this Decimal filter to remove comment and un-useful dots
class DecimalFilter(private val decimalDigits: Int) : InputFilter {
override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
var dotPos = -1
val len = spanned.length
for (decimalsI in 0 until len) {
val c = spanned[decimalsI]
if (c == '.' || c == ',') {
dotPos = decimalsI
break
}
}
if (dotPos >= 0) {
// protects against many dots
if (source == "." || source == ",") return ""
// if the text is entered before the dot
if (i4 <= dotPos) return null
if (len - dotPos > decimalDigits) return ""
}
return null
}
}
class CustomEditText : TextInputEditText {
private var decimals = 3
private var min = 0.001f
private var max = 1000f
private lateinit var oldFilters: MutableList<InputFilter>
constructor(context: Context?) : super(context!!) {
init(null)
}
constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {
init(attrs)
}
constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context!!, attrs, defStyle) {
init(attrs)
}
private fun init(attrs: AttributeSet?) {
isInEditMode
if (attrs != null) {
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.CustomEditText, 0, 0)
decimals = a.getInt(R.styleable.CustomEditText_decimals, decimals)
min = a.getFloat(R.styleable.CustomEditText_min, min)
max = a.getFloat(R.styleable.CustomEditText_max, max)
}
oldFilters = filters.toMutableList()
addFilters()
}
private fun addFilters() {
val inputFilters = ArrayList<InputFilter>(oldFilters.size 1)
inputFilters.addAll(oldFilters)
inputFilters.add(EditTextInputFilter(min, max))
inputFilters.add(DecimalFilter(decimals))
filters = inputFilters.toTypedArray()
}
fun setMin(min: Float) {
this.min = min
addFilters()
}
fun setMax(max: Float) {
this.max = max
addFilters()
}
fun setDecimals(decimals: Int) {
this.decimals = decimals
addFilters()
}
}
My problem now is, i can not enter 0
CodePudding user response:
The problem is that you're validating the field as the user types and since the range of allowed values is 0.001..1000.000 you cannot type the number 0 (even if the intention is to enter 0.1). This is not a coding but a specification problem.
The way it's specified is:
- don't accept values outside 0.001..1000.000
- validate as the user types
It's impossible to meet these requirements. I see a couple of solutions:
validate once the user submits (not as the user types)
validate without blocking the user input (by using
setError
in aTextWatcher
)allow 0 values in your filter (range 0..1000.000) but add another validation when the user submits to eliminate the 0
allow 0 values in your filter (range 0..1000.000) and disable the submit button (or whatever you have to process the input) if the value isn't in range
From a ux perspective I'd vote for the last option and maybe also indicate to the user what values are allowed so they know why they can't continue
CodePudding user response:
@EmanuelMoecklin After reading your proposal answer i reviewed my code with this code below and it works perfectly and also i can re-use it for other input field.
I use my EditTextInputFilter class to set the min and max. The DecimalFilter class to set the number of decimal
class EditTextInputFilter(min: Float, max: Float) : InputFilter {
private val min: Float = min.coerceAtMost(max)
private val max: Float = min.coerceAtLeast(max)
override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
try {
val input = (spanned.toString() source.toString()).toFloatOrZero()
if (isInRange(min, max, input)) {
return null
}
} catch (nfe: NumberFormatException) {
Logger.error(nfe.localizedMessage!!)
}
return ""
}
private fun isInRange(min: Float, max: Float, value: Float): Boolean {
return value in min..max
}
}
class DecimalFilter(private val decimalDigits: Int) : InputFilter {
override fun filter(source: CharSequence, i: Int, i2: Int, spanned: Spanned, i3: Int, i4: Int): CharSequence? {
var dotPos = -1
val len = spanned.length
for (decimalsI in 0 until len) {
val c = spanned[decimalsI]
if (c == '.' || c == ',') {
dotPos = decimalsI
break
}
}
if (dotPos >= 0) {
// protects against many dots
if (source == "." || source == ",") return ""
// if the text is entered before the dot
if (i4 <= dotPos) return null
if (len - dotPos > decimalDigits) return ""
}
return null
}
}
I've created another validate function :
private fun validateInput(input: String): Boolean {
val double = input.toDoubleOrNull()
return double != null && double in 0.001f...1000f
}
Than i set the input filter like this on my input field:
binding.valueTiet.filters = arrayOf(EditTextInputFilter(0f, 1000f), DecimalFilter(3))
binding.valueTiet.doAfterTextChanged {
if (validateInput(it)) {
binding.valueTietTil.error = ""
Logger.info("Value accepted")
} else {
binding.valueTietTil.error = "Allowed range [0.001-1000]"
}
}