Reputation: 240
im trying to make an imput to be limited to a range of numbers using the TextWatcher
and force the min and max if user try to go out of range. Works almost as expected but on a delete, allowing a visually empty text. How can i avoid that?
inputVal.addTextChangedListener(object:TextWatcher{
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) { //s=""
var curVal = s.toString().toIntOrNull()?:5 //curVal=5
if (curVal < 5){
curVal = 5
s.replace(0,s.length,"5") <- problem here, not replacing cuz s.length=0
}else if (curVal >50){
curVal = 50
s.replace(0,s.length,"50")
}
//some other things to do based on 'curVal'
}})
update what i need is, for example, u have a '20' and adds a 0 (will be 200) transform it to a '50'; and if delete a 0 (being 2) force a 5 in the input.
Upvotes: 0
Views: 301
Reputation: 240
Well, after testing i could make it work, here is the code:
class NumberWatcher(private val mMin: Int, private val mMax: Int)
:TextWatcher{
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) {
Log.d("TextWatcher","Triggered '$s'")
if (s.isEmpty() || s.toString().toInt() < mMin){
s.replace(0, s.length, mMin.toString())
return //will retrigger after the change, to avoid double excecution
}else if (s.toString().toInt() > mMax){
s.replace(0, s.length, mMax.toString())
return //will retrigger after the change, to avoid double excecution
}
Log.d("TextWatcher","Do some things with '$s'")
//do whatever
}
}
usage:
editText.addTextChangedListener(NumberWatcher(5,50))
The log:
D/TextWatcher: Triggered '45'
D/TextWatcher: Do some things with '45'
D/TextWatcher: Triggered '4'
D/TextWatcher: Triggered '5'
D/TextWatcher: Do some things with '5'
D/TextWatcher: Triggered '95'
D/TextWatcher: Triggered '50'
D/TextWatcher: Do some things with '50'
D/TextWatcher: Triggered '0'
D/TextWatcher: Triggered '5'
D/TextWatcher: Do some things with '5'
D/TextWatcher: Triggered '35'
D/TextWatcher: Do some things with '35'
D/TextWatcher: Triggered ''
D/TextWatcher: Triggered '5'
D/TextWatcher: Do some things with '5'
Upvotes: 0
Reputation: 308
You can change as per your preference; we only need to use source and dest only.
source returns the new input entered in edittext or empty if pressed backspace dest returns prev input which in buffer already
Let's say if I enter 6 first time in edittext source returns 6 and dest returns empty as no input in buffer for first time, if enter 7 next then source will return 7 but dest will return 6. Using this we can check the limit.
class InputRangeFilter(val mMin: Int, val mMax: Int) : InputFilter {
override fun filter(
source: CharSequence?,
start: Int,
end: Int,
dest: Spanned?,
dstart: Int,
dend: Int
): CharSequence? {
val finalValue = (dest.toString().trim() + source.toString().trim()).toInt()
return if (source?.isEmpty() == true && dest?.isEmpty() == true) {
""
} else if (finalValue in (mMin + 1) until mMax) {
null // keep original
} else {
if ((!TextUtils.isEmpty(source) && source.toString().trim().toInt() < mMin)
&& finalValue < mMax
) {
if (source.toString().toInt() == 0) {
""
} else {
source
}
} else {
""
}
}
}
}
Usage:
binding.edtText.filters = arrayOf(InputRangeFilter(5, 100))
Upvotes: 0
Reputation: 187
If s will be empty, the curVal will be 5.
So it won't trigger any of the conditions.
Maybe try with:
if (curVal <= 5)
Upvotes: 0