package org.solovyev.android.text.method; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.text.InputFilter; import android.text.SpannableStringBuilder; import android.text.Spanned; public class NumberInputFilter implements InputFilter { private static final int[] CHARS = new int[]{-1, -1, -1}; private static final int CHAR_SIGN = 0; private static final int CHAR_POINT = 1; private static final int CHAR_EXP = 2; private static final char[] ACCEPTED = {'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '+', '.'}; private static final NumberInputFilter INSTANCE = new NumberInputFilter(); private static boolean isSignChar(final char c) { return c == '-' || c == '+'; } private static boolean isDecimalPointChar(final char c) { return c == '.'; } private static boolean isExponentChar(final char c) { return c == 'E'; } /** * Returns a NumberInputFilter that accepts the digits 0 through 9. */ public static NumberInputFilter getInstance() { return INSTANCE; } private static boolean accepted(char c) { for (int i = ACCEPTED.length - 1; i >= 0; i--) { if (ACCEPTED[i] == c) { return true; } } return false; } public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { final CharSequence out = filterIllegalCharacters(source, start, end); if (out != null) { source = out; start = 0; end = out.length(); } CHARS[CHAR_SIGN] = -1; CHARS[CHAR_POINT] = -1; CHARS[CHAR_EXP] = -1; findChars(dest, 0, dstart, 0, CHARS); findChars(dest, dend, dest.length(), end - start, CHARS); SpannableStringBuilder filtered = null; for (int i = start; i < end; i++) { final char c = source.charAt(i); boolean filter = false; if (isSignChar(c)) { if (i == start && dstart == 0) { if (CHARS[CHAR_SIGN] >= 0) { filter = true; } else { CHARS[CHAR_SIGN] = i + dstart; } } else if (CHARS[CHAR_EXP] == i + dstart - 1) { // allow sign after exponent symbol filter = false; } else { filter = true; } } else if (isDecimalPointChar(c)) { if (CHARS[CHAR_POINT] >= 0) { filter = true; } else if (CHARS[CHAR_EXP] >= 0 && CHARS[CHAR_EXP] < i + dstart) { // no decimal point after exponent filter = true; } else { CHARS[CHAR_POINT] = i + dstart; } } else if (isExponentChar(c)) { if (CHARS[CHAR_EXP] >= 0) { filter = true; } else if (CHARS[CHAR_POINT] >= 0 && CHARS[CHAR_POINT] > i + dstart) { // no exponent before decimal point filter = true; } else if (i + dstart == 0) { // exponent can't be first filter = true; } else { CHARS[CHAR_EXP] = i + dstart; } } if (filter) { if (end == start + 1) { return ""; // Only one character, and it was stripped. } if (filtered == null) { filtered = new SpannableStringBuilder(source, start, end); } filtered.delete(i - start, i + 1 - start); } } if (filtered != null) { return filtered; } else if (out != null) { return out; } else { return null; } } private void findChars(@NonNull Spanned s, int start, int end, int offset, int[] out) { for (int i = start; i < end; i++) { final char c = s.charAt(i); if (isSignChar(c)) { if (out[CHAR_SIGN] == -1 && out[CHAR_EXP] == -1) { // count in only signs before exponent out[CHAR_SIGN] = i + offset; } } else if (isDecimalPointChar(c)) { if (out[CHAR_POINT] == -1) { out[CHAR_POINT] = i + offset; } } else if (isExponentChar(c)) { if (out[CHAR_EXP] == -1) { out[CHAR_EXP] = i + offset; } } } } @Nullable private CharSequence filterIllegalCharacters(CharSequence source, int start, int end) { final int illegal = findIllegalChar(source, start, end); if (illegal == end) { // all OK return null; } if (end - start == 1) { // it was not OK, and there is only one char, so nothing remains. return ""; } final SpannableStringBuilder filtered = new SpannableStringBuilder(source, start, end); final int newEnd = end - start - 1; // only count down to "illegal" because the chars before that were all OK. final int newIllegal = illegal - start; for (int j = newEnd; j >= newIllegal; j--) { if (!accepted(source.charAt(j))) { filtered.delete(j, j + 1); } } return filtered; } private int findIllegalChar(CharSequence s, int start, int end) { for (int i = start; i < end; i++) { if (!accepted(s.charAt(i))) { return i; } } return end; } }