Android - I want to get a number input from the user into an EditText - it needs to be separated by spaces - every 4 characters. Example: 123456781234 -> 1234 5678 1234
Simple Answer
YourEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
int len=s.toString().length();
if (before == 0 && (len == 4 || len == 9 || len == 14 ))
YourEditText.append(" ");
}
@Override
public void afterTextChanged(Editable s) {
}
});
If someone still looking for answer, check format-edit-text library
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final FormatEditText editText1 = findViewById(R.id.edit_text_1);
editText1.setFormat("---- ---- ---- ----");
}
}
Reference https://stackoverflow.com/a/59742478/1679946
I have created a class that encapsulates the given behavior.
/**
* Custom [TextWatcher] class that appends a given [separator] for every [interval].
*/
abstract class SeparatorTextWatcher(
private val separator: Char,
private val interval: Int
) : TextWatcher {
private var dirty = false
private var isDelete = false
override fun afterTextChanged(editable: Editable?) {
if (dirty) return
dirty = true
val text = editable.toString().handleSeparator()
onAfterTextChanged(text)
dirty = false
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// Empty
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
isDelete = before != 0
}
private fun String.handleSeparator(): String {
val stringBuilder = StringBuilder(this)
if (length > 0 && length.rem(interval + 1) == 0) {
if (isDelete) {
stringBuilder.deleteCharAt(length - 1)
} else {
stringBuilder.insert(length - 1, separator)
}
}
return stringBuilder.toString()
}
/**
* Subclasses must implement this method to get the formatted text.
*/
abstract fun onAfterTextChanged(text: String)
}
Here's a snippet on how to use it:
editText.addTextChangedListener(object : SeparatorTextWatcher(' ', 4) {
override fun onAfterTextChanged(text: String) {
editText.run {
setText(text)
setSelection(text.length)
}
}
})
Assuming that you know the final length of the String, you could implement a TextWatcher
this way:
override fun setUp(view: View?) {
editText.addTextChangedListener(object : TextWatcher{
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence, p1: Int, p2: Int, p3: Int) {
if(p2 == 0 && (p0.length == 4 || p0.length == 9 || p0.length == 14))
editText.append(" ")
}
override fun afterTextChanged(p0: Editable?) {
}
})
You just add a space each 4-digits block. p2 == 0
is to assure the user is not deleting, otherwise he/she would get stock.
The code is in Kotlin, You can do it exactly the same way in Java.
cleaner version of @Ario's answer which follows the DRY principle:
private int prevCount = 0;
private boolean isAtSpaceDelimiter(int currCount) {
return currCount == 4 || currCount == 9 || currCount == 14;
}
private boolean shouldIncrementOrDecrement(int currCount, boolean shouldIncrement) {
if (shouldIncrement) {
return prevCount <= currCount && isAtSpaceDelimiter(currCount);
} else {
return prevCount > currCount && isAtSpaceDelimiter(currCount);
}
}
private void appendOrStrip(String field, boolean shouldAppend) {
StringBuilder sb = new StringBuilder(field);
if (shouldAppend) {
sb.append(" ");
} else {
sb.setLength(sb.length() - 1);
}
cardNumber.setText(sb.toString());
cardNumber.setSelection(sb.length());
}
ccEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
String field = editable.toString();
int currCount = field.length();
if (shouldIncrementOrDecrement(currCount, true)){
appendOrStrip(field, true);
} else if (shouldIncrementOrDecrement(currCount, false)) {
appendOrStrip(field, false);
}
prevCount = cardNumber.getText().toString().length();
}
});
I searched lot for this here are the complete code in kotlin for card
yourEditText.addTextChangedListener(object : TextWatcher {
private val TOTAL_SYMBOLS = 19 // size of pattern 0000-0000-0000-0000
private val TOTAL_DIGITS = 16 // max numbers of digits in pattern: 0000 x 4
private val DIVIDER_MODULO =
5 // means divider position is every 5th symbol beginning with 1
private val DIVIDER_POSITION =
DIVIDER_MODULO - 1 // means divider position is every 4th symbol beginning with 0
private val DIVIDER = ' '
override fun beforeTextChanged(
s: CharSequence,
start: Int,
count: Int,
after: Int
) { // noop
}
override fun onTextChanged(
s: CharSequence,
start: Int,
before: Int,
count: Int
) { // noop
}
override fun afterTextChanged(s: Editable) {
if (!isInputCorrect(s, TOTAL_SYMBOLS, DIVIDER_MODULO, DIVIDER)) {
var repl = buildCorrectString(
getDigitArray(s, TOTAL_DIGITS),
DIVIDER_POSITION,
DIVIDER
)
yourEditText.clearFocus();
yourEditText.setText(repl);
yourEditText.requestFocus();
yourEditText.setSelection(repl!!.length);
}
}
private fun isInputCorrect(
s: Editable,
totalSymbols: Int,
dividerModulo: Int,
divider: Char
): Boolean {
var isCorrect =
s.length <= totalSymbols // check size of entered string
for (i in 0 until s.length) { // check that every element is right
isCorrect = if (i > 0 && (i + 1) % dividerModulo == 0) {
isCorrect and (divider == s[i])
} else {
isCorrect and Character.isDigit(s[i])
}
}
return isCorrect
}
private fun buildCorrectString(
digits: CharArray,
dividerPosition: Int,
divider: Char
): String? {
val formatted = StringBuilder()
for (i in digits.indices) {
if (digits[i] != '\u0000') {
formatted.append(digits[i])
if (i > 0 && i < digits.size - 1 && (i + 1) % dividerPosition == 0) {
formatted.append(divider)
}
}
}
return formatted.toString()
}
private fun getDigitArray(s: Editable, size: Int): CharArray {
val digits = CharArray(size)
var index = 0
var i = 0
while (i < s.length && index < size) {
val current = s[i]
if (Character.isDigit(current)) {
digits[index] = current
index++
}
i++
}
return digits
}
})
}