Skip to content

Instantly share code, notes, and snippets.

@starkej2
Last active September 22, 2016 20:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save starkej2/55558cb9a0eea543404677fcc0d05be4 to your computer and use it in GitHub Desktop.
Save starkej2/55558cb9a0eea543404677fcc0d05be4 to your computer and use it in GitHub Desktop.
A robust TextWatcher that groups input into the specified group sizes, with separators between them.
/**
* Formats the watched EditText to groups of characters, with separators between them.
*/
public class GroupedInputFormatWatcher implements TextWatcher {
private char separator;
private String separatorString;
private int groupSize;
/**
* Breakdown of this regex:
* ^ - Start of the string
* (\\d{4}\\s)* - A group of four digits, followed by a whitespace, e.g. "1234 ". Zero or more
* times.
* \\d{0,4} - Up to four (optional) digits.
* (?<!\\s)$ - End of the string, but NOT with a whitespace just before it.
* <p>
* Example of matching strings:
* - "2304 52"
* - "2304"
* - ""
*/
private final String regex = "^(\\d{4}\\s)*\\d{0,4}(?<!\\s)$";
private boolean isUpdating = false;
private final EditText editText;
public GroupedInputFormatWatcher(@NonNull EditText editText) {
this(editText, 4, ' ');
}
public GroupedInputFormatWatcher(@NonNull EditText editText, int groupSize, char separator) {
this.editText = editText;
this.groupSize = groupSize;
this.separator = separator;
this.separatorString = String.valueOf(separator);
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
String originalString = s.toString();
// Check if we are already updating, to avoid infinite loop.
// Also check if the string is already in a valid format.
if (isUpdating || originalString.matches(regex)) {
return;
}
// Set flag to indicate that we are updating the Editable.
isUpdating = true;
// First all whitespaces must be removed. Find the index of all whitespace.
LinkedList<Integer> spaceIndices = new LinkedList<Integer>();
for (int index = originalString.indexOf(separator); index >= 0; index = originalString.indexOf(separator, index + 1)) {
spaceIndices.offerLast(index);
}
// Delete the whitespace, starting from the end of the string and working towards the beginning.
Integer spaceIndex = null;
while (!spaceIndices.isEmpty()) {
spaceIndex = spaceIndices.removeLast();
s.delete(spaceIndex, spaceIndex + 1);
}
// Loop through the string again and add whitespaces in the correct positions
for (int i = 0; ((i + 1) * groupSize + i) < s.length(); i++) {
s.insert((i + 1) * groupSize + i, separatorString);
}
// Finally check that the cursor is not placed before a whitespace.
// This will happen if, for example, the user deleted the digit '5' in
// the string: "1234 567".
// If it is, move it back one step; otherwise it will be impossible to delete
// further numbers.
int cursorPos = editText.getSelectionStart();
if (cursorPos > 0 && s.charAt(cursorPos - 1) == separator) {
editText.setSelection(cursorPos - 1);
}
isUpdating = false;
}
}
@starkej2
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment