Skip to content

Instantly share code, notes, and snippets.

@nasserkhosravi
Last active May 31, 2020 08:12
Show Gist options
  • Save nasserkhosravi/a5c1c5421bde542e57271565c9941c6d to your computer and use it in GitHub Desktop.
Save nasserkhosravi/a5c1c5421bde542e57271565c9941c6d to your computer and use it in GitHub Desktop.
//- Add separator to text each 3 number //- Handle selection when selection position is after separator
package com.nasserkhosravi.uikit
import android.text.Editable
import android.text.SpannableStringBuilder
import android.text.TextWatcher
import android.widget.EditText
import android.widget.TextView
/**
* Your [EditText] should have ltr layout direction
*/
class NumberTextWatcher(
private val edView: EditText,
private val listener: ((EditText, CharSequence?) -> Unit)?
) : TextWatcher {
private var beforeText: String = ""
override fun afterTextChanged(s: Editable?) {
listener?.invoke(edView, s.toString())
}
/*
* start: is the start index of the red highlighted text (that is about to be deleted)
* count: is the length of the red highlighted text (that is about to be deleted)
* after: is the length of the green highlighted text (that is about to be added)
* */
override fun beforeTextChanged(p0: CharSequence?, start: Int, count: Int, after: Int) {
beforeText = p0.toString()
}
/*
* start: is the start index of the green highlighted text (that just got added).
* This is the same as the start of beforeTextChanged.
* before: is the length of the red highlighted text (that just got deleted).
* This is the same as the count of beforeTextChanged.
* count: is the length of the green highlighted text (that just got added).
* This is the same as the after of beforeTextChanged.
* */
override fun onTextChanged(p0: CharSequence?, start: Int, before: Int, count: Int) {
if (p0.isNullOrEmpty()) {
return
}
// 1. get cursor position : p0 = start + before
val initialCursorPosition = start + before
val targetString = if (isSeparatorRemoved(p0)) {
StringBuilder(p0).deleteCharAt(initialCursorPosition - 2).toString()
} else {
p0
}
//2. get digit count after cursor position : c0
val numOfDigitsToRightOfCursor = getNumberOfDigits(
beforeText.substring(
initialCursorPosition,
beforeText.length
)
)
val newAmount = formatAmount(targetString.toString())
edView.removeTextChangedListener(this)
if (newAmount.toString() == "0")
edView.setText("")
else {
edView.setText(newAmount, TextView.BufferType.SPANNABLE)
//set new cursor position
edView.setSelection(getNewCursorPosition(numOfDigitsToRightOfCursor, newAmount.toString()))
}
edView.addTextChangedListener(this)
}
private fun isSeparatorRemoved(p0: CharSequence): Boolean {
val beforeDigit = getNumberOfDigits(beforeText)
val newDigit = getNumberOfDigits(p0.toString())
return newDigit == beforeDigit
}
private fun formatAmount(amount: String): SpannableStringBuilder {
val sNonNumeric = removeNonNumeric(amount)
val result = sNonNumeric.formatEachBySeparator(3, ",")
return SpannableStringBuilder(result)
}
private fun removeNonNumeric(numberString: String): String {
val strings = StringBuilder()
for (i in numberString) {
if (i.isDigit()) {
strings.append(i)
}
}
return strings.toString()
}
private fun getNewCursorPosition(digitCountToRightOfCursor: Int, numberString: String): Int {
var position = 0
var c = digitCountToRightOfCursor
for (i in numberString.reversed()) {
if (c == 0)
break
if (i.isDigit())
c--
position++
}
return numberString.length - position
}
private fun getNumberOfDigits(text: String): Int {
var count = 0
for (i in text)
if (i.isDigit())
count++
return count
}
private fun String.formatEachBySeparator(each: Int, separator: String): String {
val target = this
val result = StringBuilder()
for (index in target.indices) {
if (index % each == 0) {
if (index > 0) {
result.append(separator)
}
}
result.append(target[index])
}
return result.toString()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment