I want to override getText() of EditText.
I receive this kind of String: "12,345,678"
My purpose is to just remove the commas and return the Editable but when with my code I get an error.
public class AmountEditText extends EditText {
#Override
public Editable getText() {
Editable s = super.getText();
if(s!=null && s.length()>0) {
if (s.toString().contains(",")) {
return new SpannableStringBuilder(s.toString().replace(",", ""));
}
}
return s;
}
private TextWatcher watcher = 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 position = getSelectionStart();
int nbCommaBefore;
int nbCommaAfter;
String str = s.toString();
String finalStr;
String formattedStr;
nbCommaBefore = str.length() - str.replace(",", "").length();
boolean containsDot = false;
if (str.contains(".")) {
containsDot = true;
formattedStr = str.split("\\.")[0];
} else {
formattedStr = str;
}
if (!s.toString().isEmpty()) {
removeTextChangedListener(watcher);
formattedStr = formattedStr.replace(",", "");
formattedStr = formattedStr.replaceAll("(\\d)(?=(\\d{3})+$)", "$1,");
if (containsDot) {
if (str.split("\\.").length != 1) {
finalStr = formattedStr + "." + str.split("\\.")[1].replace(",", "");
} else {
finalStr = formattedStr + ".";
}
} else {
finalStr = formattedStr;
}
nbCommaAfter = finalStr.length() - finalStr.replace(",", "").length();
setText(finalStr);
if (position == str.length()){
setSelection(finalStr.length());
}
else if (position == 0)
{
setSelection(0);
}
else if (nbCommaBefore < nbCommaAfter){
setSelection(position + 1);
}
else if (nbCommaAfter < nbCommaBefore){
setSelection(position - 1);
}
else{
setSelection(position);
}
addTextChangedListener(watcher);
}
}
#Override
public void afterTextChanged(Editable s) {
}
};
public AmountEditText(Context context) {
this(context, null);
}
public AmountEditText(Context context, AttributeSet attrs) {
super(context, attrs);
addTextChangedListener(watcher);
}
public AmountEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
addTextChangedListener(watcher);
}
}
E/MessageQueue-JNI: Exception in MessageQueue callback:
handleReceiveCallback E/MessageQueue-JNI:
java.lang.IndexOutOfBoundsException: setSpan (0 ... 5) ends beyond
length 4
at android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java:1265)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:684)
at android.text.SpannableStringBuilder.setSpan(SpannableStringBuilder.java:677)
at android.widget.SpellChecker$SpellParser.setRangeSpan(SpellChecker.java:532)
at android.widget.SpellChecker$SpellParser.parse(SpellChecker.java:515)
at android.widget.SpellChecker.spellCheck(SpellChecker.java:242)
at android.widget.Editor.updateSpellCheckSpans(Editor.java:679)
at android.widget.Editor.sendOnTextChanged(Editor.java:1249)
at android.widget.TextView.sendOnTextChanged(TextView.java:8191)
at android.widget.TextView.setText(TextView.java:4483)
at android.widget.TextView.setText(TextView.java:4337)
at android.widget.EditText.setText(EditText.java:89)
at android.widget.TextView.setText(TextView.java:4312)
at org.newtonproject.newpay.widgetlib.AmountEditText$1.onTextChanged(AmountEditText.java:74)
I would like to precise that the error doesn't come from my onTextChanged
because everything works well without the getText() override
EDIT : The user can enter number, I will append some commas in order to format the number. But when I override getText() I want to delete these commas in that way I don't have to filter the return of getText() everytime
Ok, I debugged that and found out that the problem was on that line
if (position == str.length()){
setSelection(finalStr.length());
}
lenght() is out of bound for a set selection, since it's 0 based
just change your code with that and it will work properly
if (position == str.length()){
setSelection(finalStr.length() - 1);
}
If needed, full code here (I used AppCompatEditText, but it's the same):
public class AmountEditText extends android.support.v7.widget.AppCompatEditText {
#Override
public Editable getText() {
Editable s = super.getText();
if(s!=null && s.length()>0) {
if (s.toString().contains(",")) {
return new SpannableStringBuilder(s.toString().replace(",", ""));
}
}
return s;
}
private TextWatcher watcher = 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 position = getSelectionStart();
int nbCommaBefore;
int nbCommaAfter;
String str = s.toString();
String finalStr;
String formattedStr;
nbCommaBefore = str.length() - str.replace(",", "").length();
boolean containsDot = false;
if (str.contains(".")) {
containsDot = true;
formattedStr = str.split("\\.")[0];
} else {
formattedStr = str;
}
if (!s.toString().isEmpty()) {
removeTextChangedListener(watcher);
formattedStr = formattedStr.replace(",", "");
formattedStr = formattedStr.replaceAll("(\\d)(?=(\\d{3})+$)", "$1,");
if (containsDot) {
if (str.split("\\.").length != 1) {
finalStr = formattedStr + "." + str.split("\\.")[1].replace(",", "");
} else {
finalStr = formattedStr + ".";
}
} else {
finalStr = formattedStr;
}
nbCommaAfter = finalStr.length() - finalStr.replace(",", "").length();
setText(finalStr);
if (position == str.length()){
setSelection(finalStr.length() - 1);
}
else if (position == 0)
{
setSelection(0);
}
else if (nbCommaBefore < nbCommaAfter){
setSelection(position + 1);
}
else if (nbCommaAfter < nbCommaBefore){
setSelection(position - 1);
}
else{
setSelection(position);
}
addTextChangedListener(watcher);
}
}
#Override
public void afterTextChanged(Editable s) {
}
};
public AmountEditText(Context context) {
this(context, null);
}
public AmountEditText(Context context, AttributeSet attrs) {
super(context, attrs);
addTextChangedListener(watcher);
}
public AmountEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
addTextChangedListener(watcher);
}
}
Let me know if that helped!
In your case, you can not override getText() and resize and using TextWatcher at same time.
Check the android source code below and you will why
SpannableStringBuilder.java
public void setSpan(Object what, int start, int end, int flags) {
setSpan(true, what, start, end, flags, true/*enforceParagraph*/);
}
private void setSpan(boolean send, Object what, int start, int end, int flags,
boolean enforceParagraph) {
checkRange("setSpan", start, end);
}
private void checkRange(final String operation, int start, int end) {
...
int len = length();
if (start > len || end > len) {
throw new IndexOutOfBoundsException(operation + " " +
region(start, end) + " ends beyond length " + len); // here is you exception
}
}
public int length() {
return mText.length - mGapLength;
}
SpellChecker.java
private void setRangeSpan(Editable editable, int start, int end) {
...
editable.setSpan(mRange, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
public void parse(int start, int end) {
...
if (parseEnd > start) {
setRangeSpan((Editable) mTextView.getText(), start, parseEnd); // I think the error happened from here, they use your getText() function here and receive shorter string, but the start, parseEnd still stick with original string
parse();
}
}
Solution .
You can simple defind a new function like getBeautifulText().
Try this one it will help's you
editText.addTextChangedListener(new TextWatcher() {
boolean isEdiging;
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void afterTextChanged(Editable s) {
if (isEdiging) return;
isEdiging = true;
String str = s.toString().replaceAll("[^\\d]", "");
double s1 = 0;
try {
s1 = Double.parseDouble(str);
} catch (NumberFormatException e) {
e.printStackTrace();
}
NumberFormat nf2 = NumberFormat.getInstance(Locale.ENGLISH);
((DecimalFormat) nf2).applyPattern("###,###.###");
s.replace(0, s.length(), nf2.format(s1));
if (s.toString().equals("0")) {
editText.setText("");
}
isEdiging = false;
}
});
Based on the requirements in your question:
The user can enter number, I will append some commas in order to
format the number. But when I override getText() I want to delete
these commas
I believe you could use a much simpler solution involving DecimalFormat:
class Formatter {
private final DecimalFormat f = new DecimalFormat(",###");
private final DecimalFormat o = new DecimalFormat("#");
String withCommas(String in) {
try {
return withCommas(Long.parseLong(in));
} catch (NumberFormatException e) {
e.printStackTrace();
return withCommas(Long.MIN_VALUE);
}
}
String withCommas(long in) {
return f.format(in);
}
Number stripCommas(String in) {
try {
return f.parse(in);
} catch (ParseException e) {
return Long.MIN_VALUE;
}
}
String stripCommasAsString(String in) {
return o.format(stripCommas(in));
}
}
Which gives:
final long num = 12345678L;
final Formatter f = new Formatter();
assertEquals("12,345,678", f.withCommas("12345678"));
assertEquals("12,345,678", f.withCommas(num));
assertEquals(num, f.stripCommas("12,345,678");
assertEquals("12345678", f.stripCommasAsString("12,345,678"));
Related
I am formatting edit text field as per US currency format, where while typing number in a field, let's say "12345678" it appears like "12,345,678".
For this I have used TextWatcher and on afterTextChanged(...) method I am formatting the entered text like:
#Override
public void afterTextChanged(Editable editable) {
String str = editable.toString();
String number = str.replaceAll("[,]", "");
if (number.equals(previousNumber) || number.isEmpty()) {
return;
}
previousNumber = number;
DecimalFormat formatter = new DecimalFormat("#,###.##", new DecimalFormatSymbols(Locale.US));
String formattedString = formatter.format(number);
editText.setText(formattedString);
}
Also, I am using onSelectionChanged(...) callback method like:
#Override
protected void onSelectionChanged(int selStart, int selEnd) {
this.setSelection(selStart);
}
But here this 'selStart' doesn't return the actual length of number as it excludes the number of "," in every currency.
For example: for "12,345,678" it returns count as 8 instead of 10.
That's why I am not able to place my cursor at the end of the field.
Following is the code of custom EditText, which I am using:
public class CurrencyEditText extends AppCompatEditText {
private static final int MAX_LENGTH = 16;
private static final int MAX_DECIMAL_DIGIT = 2;
private static String prefix = "";
private CurrencyTextWatcher currencyTextWatcher = new CurrencyTextWatcher(this, prefix);
public CurrencyEditText(Context context) {
this(context, null);
}
public CurrencyEditText(Context context, AttributeSet attrs) {
this(context, attrs, android.support.v7.appcompat.R.attr.editTextStyle);
}
public CurrencyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
}
#Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
if (focused) {
this.addTextChangedListener(currencyTextWatcher);
} else {
this.removeTextChangedListener(currencyTextWatcher);
}
handleCaseCurrencyEmpty(focused);
}
private void handleCaseCurrencyEmpty(boolean focused) {
if (!focused) {
if (getText().toString().equals(prefix)) {
setText("");
}
}
}
private static class CurrencyTextWatcher implements TextWatcher {
private final EditText editText;
DecimalFormat formatter;
private String previousNumber;
private String prefix;
Context mContext;
CurrencyTextWatcher(EditText editText, String prefix) {
this.editText = editText;
this.prefix = prefix;
formatter = new DecimalFormat("#,###.##", new DecimalFormatSymbols(Locale.US));
}
#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 editable) {
String str = editable.toString();
String number = str.replaceAll("[,]", "");
if (number.equals(previousNumber) || number.isEmpty()) {
return;
}
previousNumber = number;
String formattedString = prefix + formatNumber(number);
editText.removeTextChangedListener(this);
editText.setText(formattedString);
//handleSelection();
editText.addTextChangedListener(this);
}
private String formatNumber(String number) {
if (number.contains(".")) {
return formatDecimal(number);
}
return formatInteger(number);
}
private String formatInteger(String str) {
BigDecimal parsed = new BigDecimal(str);
return formatter.format(parsed);
}
private String formatDecimal(String str) {
if (str.equals(".")) {
return "0.";
}
BigDecimal parsed = new BigDecimal(str);
DecimalFormat formatter = new DecimalFormat("#,##0." + getDecimalPattern(str),
new DecimalFormatSymbols(Locale.US));
formatter.setRoundingMode(RoundingMode.DOWN);
return formatter.format(parsed);
}
private String getDecimalPattern(String str) {
int decimalCount = str.length() - str.indexOf(".") - 1;
StringBuilder decimalPattern = new StringBuilder();
for (int i = 0; i < decimalCount && i < MAX_DECIMAL_DIGIT; i++) {
decimalPattern.append("0");
}
return decimalPattern.toString();
}
/*private void handleSelection() {
if (editText.getText().length() <= MAX_LENGTH) {
editText.setSelection(editText.getText().length());
}
}*/
}
#Override
protected void onSelectionChanged(int selStart, int selEnd) {
this.setSelection(selStart);
}
}
I don't want to use this.setSelection(lengthOfTheEnteredText) because it created issue when you edit the field!
What could be the reason that onSelectionChanged(...) does not consider the count of "," present in number?
After exploring more on this issue, I have found the solution. Where I am calculating the cursor position. I have removed onSelectionChanged(...) method from my code and I am handling selection inafterTextChanged(...) method. In the following code I have made changes in afterTextChanged(...) :
#Override
public void afterTextChanged(Editable editable) {
String str = editable.toString();
String number = str.replaceAll("[,]", "");
if (number.equals(previousNumber) || number.isEmpty()) {
return;
}
previousNumber = number;
int startText, endText;
startText = editText.getText().length();
int selectionStart = editText.getSelectionStart();
String formattedString = prefix + formatNumber(number);
editText.removeTextChangedListener(this);
editText.setText(formattedString);
endText = editText.getText().length();
int selection = (selectionStart + (endText - startText));
editText.setSelection(selection);
editText.addTextChangedListener(this);
}
I want to separate automatically edit text like (9,99,999) like this. i searched for this on web but i am not getting proper solution for this.
can you please help me.thank you stack overflow.
You can use DecimalFormat for this like below Code:
public String formatNumber(double d) {
DecimalFormat formatter = (DecimalFormat) NumberFormat.getInstance(Locale.US);
formatter.applyPattern("#,###");
return formatter.format(d);
}
You Can Pass Pattern as you want.
public static String formatCurrency(String number) {
try {
number = NumberFormat.getNumberInstance(Locale.US).format(Double.valueOf(number));
} catch (Exception e) {
}
return number;
}
This is what i did. Works perfectly
Try this.
public class NumberTextWatcherForThousand implements TextWatcher {
EditText editText;
public NumberTextWatcherForThousand(EditText editText) {
this.editText = editText;
}
#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) {
try
{
editText.removeTextChangedListener(this);
String value = editText.getText().toString();
if (value != null && !value.equals(""))
{
if(value.startsWith(".")){ //adds "0." when only "." is pressed on begining of writting
editText.setText("0.");
}
if(value.startsWith("0") && !value.startsWith("0.")){
editText.setText(""); //Prevents "0" while starting but not "0."
}
String str = editText.getText().toString().replaceAll(",", "");
if (!value.equals(""))
editText.setText(getDecimalFormat(str));
editText.setSelection(editText.getText().toString().length());
}
editText.addTextChangedListener(this);
return;
}
catch (Exception ex)
{
ex.printStackTrace();
editText.addTextChangedListener(this);
}
}
public static String getDecimalFormat(String value)
{
StringTokenizer lst = new StringTokenizer(value, ".");
String str1 = value;
String str2 = "";
if (lst.countTokens() > 1)
{
str1 = lst.nextToken();
str2 = lst.nextToken();
}
String str3 = "";
int i = 0;
int j = -1 + str1.length();
if (str1.charAt( -1 + str1.length()) == '.')
{
j--;
str3 = ".";
}
for (int k = j;; k--)
{
if (k < 0)
{
if (str2.length() > 0)
str3 = str3 + "." + str2;
return str3;
}
if (i == 3)
{
str3 = "," + str3;
i = 0;
}
str3 = str1.charAt(k) + str3;
i++;
}
}
//Trims all the comma of the string and returns
public static String trimCommaOfString(String string) {
if(string.contains(",")){
return string.replace(",","");}
else {
return string;
}
}
}
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
editText.removeTextChangedListener(this);
try {
StringBuilder originalString = new StringBuilder(editable.toString().replaceAll(",", ""));
int indx = 0;
for (int i = originalString.length(); i > 0; i--) {
if (indx % 3 == 0 && indx > 0)
originalString = originalString.insert(i, ",");
indx++;
}
editText.setText(originalString);
editText.setSelection(originalString.length());
} catch (NumberFormatException nfe) {
nfe.printStackTrace();
}
editText.addTextChangedListener(this);
}
});
I got it working with custom '#' tokenizer. But it fails for the first autocompletion. My code works as a comma tokenizer where I get suggestion for any character and next suggestion only after a comma(it's '#' in my case).
Here's my code.
String[] str={"Andoid","Jelly Bean","Froyo",
"Ginger Bread","Eclipse Indigo","Eclipse Juno"};
editEmojicon.setTokenizer(new MultiAutoCompleteTextView.Tokenizer() {
#Override
public int findTokenStart(CharSequence text, int cursor) {
int i = cursor;
while (i > 0 && text.charAt(i - 1) != '#') {
i--;
}
while (i < cursor && text.charAt(i) == ' ') {
i++;
}
return i;
}
#Override
public int findTokenEnd(CharSequence text, int cursor) {
int i = cursor;
int len = text.length();
while (i < len) {
if (text.charAt(i) == ' ') {
return i;
} else {
i++;
}
}
return len;
}
#Override
public CharSequence terminateToken(CharSequence text) {
int i = text.length();
while (i > 0 && text.charAt(i - 1) == ' ') {
i--;
}
if (i > 0 && text.charAt(i - 1) == ' ') {
return text;
} else {
if (text instanceof Spanned) {
SpannableString sp = new SpannableString(text + " ");
TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
Object.class, sp, 0);
return sp;
} else {
return text+" ";
}
}
}
});
ArrayAdapter<String> adp=new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line,str);
editEmojicon.setThreshold(1);
editEmojicon.setAdapter(adp);
Saw this post here But didn't work.
I will be grateful if someone could help me with this.
EmojiconEditText.java
public class EmojiconEditText extends MultiAutoCompleteTextView {
private int mEmojiconSize;
public EmojiconEditText(Context context) {
super(context);
mEmojiconSize = (int) getTextSize();
}
public EmojiconEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public EmojiconEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Emojicon);
mEmojiconSize = (int) a.getDimension(R.styleable.Emojicon_emojiconSize, getTextSize());
a.recycle();
setText(getText());
}
#Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
EmojiconHandler.addEmojis(getContext(), getText(), mEmojiconSize);
}
/**
* Set the size of emojicon in pixels.
*/
public void setEmojiconSize(int pixels) {
mEmojiconSize = pixels;
}
/** For Mention System / Added by midhun **/
#Override
protected void performFiltering(CharSequence text, int start, int end, int keyCode) {
if (text.charAt(start) == '#') {
start = start + 1;
} else {
text = text.subSequence(0, start);
for (int i = start; i < end; i++) {
text = text + "*";
}
}
super.performFiltering(text, start, end, keyCode);
}
}
If you want to do mention query with the character '#', you can do query onTextChanged as below:
#Override
public void onTextChanged(CharSequence s, int start, int before, final int count) {
if (s.length() > 0) {
// Todo: query mentions
Matcher mentionMatcher = Pattern.compile("#([A-Za-z0-9_-]+)").matcher(s.toString());
// while matching
while (mentionMatcher.find()) {
yourSearchText = s.toString().substring(mentionMatcher.start() + 1, mentionMatcher.end());
// do query with yourSearchText below
}
}
}
Note: you also can do hashTag query with the character '#' by replace it in
"#([A-Za-z0-9_-]+)"
https://github.com/linkedin/Spyglass
check this out , having all the use cases for mentions
I am writing a Convertor Application and I want a thousand separator automatically added to the digits in realtime, so after I implemented this applypattern code on the TextWatcher, now I can not make floationg point inputs.....here is my code for the Editext
am2 = new TextWatcher()
{
boolean isEdiging;
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
public void afterTextChanged(Editable s) {
if (s.toString().equals("")) {
amount.setText("");
value = 0;
}else{
if(isEdiging) return;
isEdiging = true;
StringBuffer strBuff = new StringBuffer();
char c;
for (int i = 0; i < amount2.getText().toString().length() ; i++) {
c = amount2.getText().toString().charAt(i);
if (Character.isDigit(c)) {
strBuff.append(c);
}
}
value = Double.parseDouble(strBuff.toString());
reverse();
NumberFormat nf2 = NumberFormat.getInstance(Locale.ENGLISH);
((DecimalFormat)nf2).applyPattern("###,###.#######");
s.replace(0, s.length(), nf2.format(value));
isEdiging = false;
}
}
};
So is there any way of inputting floating point within the EditText?
This class solves the problem
public class NumberTextWatcher implements TextWatcher {
private DecimalFormat df;
private DecimalFormat dfnd;
private boolean hasFractionalPart;
private EditText et;
public NumberTextWatcher(EditText et)
{
df = new DecimalFormat("#,###.##");
df.setDecimalSeparatorAlwaysShown(true);
dfnd = new DecimalFormat("#,###");
this.et = et;
hasFractionalPart = false;
}
#SuppressWarnings("unused")
private static final String TAG = "NumberTextWatcher";
public void afterTextChanged(Editable s)
{
et.removeTextChangedListener(this);
try {
int inilen, endlen;
inilen = et.getText().length();
String v = s.toString().replace(String.valueOf(df.getDecimalFormatSymbols().getGroupingSeparator()), "");
Number n = df.parse(v);
int cp = et.getSelectionStart();
if (hasFractionalPart) {
et.setText(df.format(n));
} else {
et.setText(dfnd.format(n));
}
endlen = et.getText().length();
int sel = (cp + (endlen - inilen));
if (sel > 0 && sel <= et.getText().length()) {
et.setSelection(sel);
} else {
// place cursor at the end?
et.setSelection(et.getText().length() - 1);
}
} catch (NumberFormatException nfe) {
// do nothing?
} catch (ParseException e) {
// do nothing?
}
et.addTextChangedListener(this);
}
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{
}
public void onTextChanged(CharSequence s, int start, int before, int count)
{
if (s.toString().contains(String.valueOf(df.getDecimalFormatSymbols().getDecimalSeparator())))
{
hasFractionalPart = true;
} else {
hasFractionalPart = false;
}
}
}
I'm totally new to Android.
I would like to put in a textbox where user can enter an IP address ... but how do I limit the user to only enter numbers? ... and how do I validate?
Is there a ready-made-ip-address-textbox "out there" I can use?
Thanks!
Mojo
What I've found that works is to set an EditText to use android:inputType="phone", so the input is restricted to digits, period, and a handful of other characters. However, this will only let you input IPV4 addresses, since it's digits only. For validation, you'll have to get the input text and parse it by hand.
As far as ready-made input widgets, I haven't come across one.
In addition to what Erich said, you can use android:digits="0123456789." to disallow anything but digits and a decimal point.
You can use :
EditText ipAddress = (EditText)findViewById(R.id.ip_address);
InputFilter[] filters = new InputFilter[1];
filters[0] = new InputFilter() {
#Override
public CharSequence filter(CharSequence source, int start, int end,
android.text.Spanned dest, int dstart, int dend) {
if (end > start) {
String destTxt = dest.toString();
String resultingTxt = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
if (!resultingTxt.matches ("^\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3})?)?)?)?)?)?")) {
return "";
} else {
String[] splits = resultingTxt.split("\\.");
for (int i=0; i<splits.length; i++) {
if (Integer.valueOf(splits[i]) > 255) {
return "";
}
}
}
}
return null;
}
};
ipAddress.setFilters(filters);
For validation, regular-expressions.info has a good regex string you could use for testing for an IP in a valid (0-255) range:
\b(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b
This is, I think, the most complete of the existing solutions to this (at least that I've found). The only improvement I can imagine is to implement a new KeyListener to restrain the input better but I'm not convinced it is practically possible, given how IMEs work with layouts and stuff.
public class IPAddressText extends EditText {
public IPAddressText(Context context) {
super(context);
setInputType(InputType.TYPE_CLASS_PHONE);
setFilters(new InputFilter[] { new InputFilter(){
#Override
public CharSequence filter(CharSequence source, int start, int end, android.text.Spanned dest, int dstart, int dend) {
if (end > start) {
String destTxt = dest.toString();
String resultingTxt = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
if (!resultingTxt.matches("^\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3})?)?)?)?)?)?")) {
return "";
}
else {
String[] splits = resultingTxt.split("\\.");
for (int i = 0; i < splits.length; i++) {
if (Integer.valueOf(splits[i]) > 255) {
return "";
}
}
}
}
return null;
}
}
});
addTextChangedListener(new TextWatcher(){
boolean deleting = false;
int lastCount = 0;
#Override
public void afterTextChanged(Editable s) {
if (!deleting) {
String working = s.toString();
String[] split = working.split("\\.");
String string = split[split.length - 1];
if (string.length() == 3 || string.equalsIgnoreCase("0")
|| (string.length() == 2 && Character.getNumericValue(string.charAt(0)) > 1)) {
s.append('.');
return;
}
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (lastCount < count) {
deleting = false;
}
else {
deleting = true;
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Nothing happens here
}
});
}
}
And because it is what I actually ended up using, here is an EditTextPreference version:
public class IPAddressPreference extends EditTextPreference {
public IPAddressPreference(Context context) {
super(context);
getEditText().setInputType(InputType.TYPE_CLASS_PHONE);
getEditText().setFilters(new InputFilter[] { new InputFilter(){
#Override
public CharSequence filter(CharSequence source, int start, int end, android.text.Spanned dest, int dstart, int dend) {
if (end > start) {
String destTxt = dest.toString();
String resultingTxt = destTxt.substring(0, dstart) + source.subSequence(start, end) + destTxt.substring(dend);
if (!resultingTxt.matches("^\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3})?)?)?)?)?)?")) {
return "";
}
else {
String[] splits = resultingTxt.split("\\.");
for (int i = 0; i < splits.length; i++) {
if (Integer.valueOf(splits[i]) > 255) {
return "";
}
}
}
}
return null;
}
}
});
getEditText().addTextChangedListener(new TextWatcher(){
boolean deleting = false;
int lastCount = 0;
#Override
public void afterTextChanged(Editable s) {
if (!deleting) {
String working = s.toString();
String[] split = working.split("\\.");
String string = split[split.length - 1];
if (string.length() == 3 || string.equalsIgnoreCase("0")
|| (string.length() == 2 && Character.getNumericValue(string.charAt(0)) > 1)) {
s.append('.');
return;
}
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (lastCount < count) {
deleting = false;
}
else {
deleting = true;
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Nothing happens here
}
});
}
}
I'm using this TextWatcher:
public class IPTextWatcher implements TextWatcher
{
private static String LOG_TAG = "IPTextWatcher";
private EditText editText;
private BarcodeTextWatcher.ChangeListener listener = null;
public IPTextWatcher( EditText editText )
{
this.editText = editText;
}
private static final String IPADDRESS_PATTERN =
"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";
#Override
public void afterTextChanged( Editable s )
{
Log.v( LOG_TAG, s.toString() );
if( !s.toString().matches( IPADDRESS_PATTERN ) )
{
String ip = format( s.toString() );
editText.removeTextChangedListener( this );
editText.setText( ip );
editText.setTextKeepState( ip );
Selection.setSelection( editText.getText(), ip.length() );
editText.addTextChangedListener( this );
if( listener != null )
listener.onChange();
}
}
public static String format( String value )
{
String userInput = "" + value.replaceAll( "[^\\d\\.]", "" );
StringBuilder ipBuilder = new StringBuilder();
String[] address = userInput.split("\\.");
String glue = null;
for( String part : address )
{
if( glue != null ) ipBuilder.append( glue );
int p = Integer.valueOf( part );
if( p >= 256 )
{
int i = 1;
do
{
p = Integer.valueOf( part.substring( 0, part.length() -i ) );
i++;
}
while( p >= 256 );
}
ipBuilder.append( p );
glue = ".";
}
if( userInput.charAt( userInput.length()-1 ) == '.' )
ipBuilder.append( "." );
return ipBuilder.toString();
}
#Override
public void onTextChanged( CharSequence s, int start, int before, int count )
{
}
#Override
public void beforeTextChanged( CharSequence s, int start, int count, int after )
{
}
}
<EditText
android:id="#+id/ip_address"
android:inputType="number|numberDecimal"
android:digits="0123456789."
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
worked for me
Based on NathanOliver's and enneract's code I wrote, in kotlin, a variation that insertes a dot when one is deleted.
class IpAddressEditText : AppCompatEditText {
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init()
}
private fun init() {
// phone input type to show the numeric keyboard in all kinds of devices
inputType = InputType.TYPE_CLASS_PHONE
// restrict the input of the characters specified in string bellow
keyListener = DigitsKeyListener.getInstance("0123456789.")
// InputFilter decides what can be typed
filters = arrayOf(InputFilter { source, start, end, dest, dstart, dend ->
if (end > start) {
val inputString = dest.toString()
val substring = inputString.substring(0, dstart) + source.subSequence(start, end) + inputString.substring(dend)
if (!substring.matches("^\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3}(\\.(\\d{1,3})?)?)?)?)?)?".toRegex())) {
// do not allow the input of:
// segments with more than 3 characters and less than 1;
// segments != 4;
return#InputFilter ""
} else {
val splits = substring.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
for (i in splits.indices) {
// don't allow a segment with a value more than 255
if (Integer.valueOf(splits[i]) > 255) {
return#InputFilter ""
}
}
}
}
null
})
// TextWatcher notifies what was typed
addTextChangedListener(object : TextWatcher {
var isDeleting = false
var lastCount = 0
override fun afterTextChanged(editable: Editable) {
if (!isDeleting) {
val inputString = editable.toString()
val segmentList = inputString.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val lastSegment = segmentList[segmentList.size - 1]
// each segment of the ip can have max size of 3 characters and a max value of 255
if (lastSegment.length == 3 || lastSegment.length == 2 && Integer.parseInt(lastSegment) > 25) {
// add a dot automatically if the conditions met
editable.append('.')
return
}
} else {
// add a dot in the same position where it was deleted
editable.insert(selectionStart, ".")
}
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
isDeleting = lastCount >= count
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
// do nothing
}
})
}
}
Here is the code that limits a user to only enter numbers and dots by displaying a soft keyboard with only numbers and a dot (but allows you to enter multiple dots).
etIpAddress.setInputType(InputType.TYPE_CLASS_NUMBER);
etIpAddress.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL);
etIpAddress.setKeyListener(DigitsKeyListener.getInstance(false,false));
etIpAddress.setKeyListener(DigitsKeyListener.getInstance("0123456789."));