android numberpicker can't read keyboard number input - android

I'm trying to create a numberpicker to select a month.
It works aslong as I select the value through scrolling or if i use the keyboard to input the month by text (e.g. "jan" for januari)
I also want my users to be able to input '1' to select januari.
From numberpicker source code, it seems this should be possible:
/**
* #return The selected index given its displayed <code>value</code>.
*/
private int getSelectedPos(String value) {
if (mDisplayedValues == null) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
// Ignore as if it's not a number we don't care
}
} else {
for (int i = 0; i < mDisplayedValues.length; i++) {
// Don't force the user to type in jan when ja will do
value = value.toLowerCase();
if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
return mMinValue + i;
}
}
/*
* The user might have typed in a number into the month field i.e.
* 10 instead of OCT so support that too.
*/
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
// Ignore as if it's not a number we don't care
}
}
return mMinValue;
}
The problem is, if I try to input a number, the EditText just stays empty.
This is how I initialise my numberpicker:
//getting the months using Calendar
Calendar cal = Calendar.getInstance();
Map<String, Integer> monthMap = cal.getDisplayNames(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
TreeMap<Integer, String> sorted = new TreeMap<>();
for(Map.Entry<String, Integer> entry : monthMap.entrySet()) {
sorted.put(entry.getValue(), entry.getKey());
}
String[] displayNames = sorted.values().toArray(new String[]{});
mMonthPicker.setMinValue(0);
mMonthPicker.setMaxValue(11);
mMonthPicker.setDisplayedValues(displayNames);
mMonthPicker.setWrapSelectorWheel(false);
I tried setting the inputtype for the edittext to InputType.TYPE_NULL using the answer given here but that didn't change anything.
The Edit Text stays empty if I try to input a number.

I finally figured it out. The numberpicker didn't allow numbers for month input because of the filter on the EditText.
I solved it by copying some code of the NumberKeyListener from numberpicker source code and adjusting it so it would accept numeric input.
Then i added this filter on the EditText, which I look up by going through the childviews and checking if the current view is an EditText.
I found the code for looking up the EditText in the answer here: NumberPicker doesn't work with keyboard
my code looks like this:
mInputText = findInput(mMonthPicker);
mInputText.setFilters(new InputFilter[]{ new InputTextFilter() });
this is my filter:
/**
* Filter for accepting only valid indices or prefixes of the string
* representation of valid indices.
*/
class InputTextFilter extends NumberKeyListener {
/**
* The numbers accepted by the input text's {#link android.view.LayoutInflater.Filter}
*/
private final char[] DIGIT_CHARACTERS = new char[] {
// Latin digits are the common case
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
// Arabic-Indic
'\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668'
, '\u0669',
// Extended Arabic-Indic
'\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8'
, '\u06f9',
// Hindi and Marathi (Devanagari script)
'\u0966', '\u0967', '\u0968', '\u0969', '\u096a', '\u096b', '\u096c', '\u096d', '\u096e'
, '\u096f',
// Bengali
'\u09e6', '\u09e7', '\u09e8', '\u09e9', '\u09ea', '\u09eb', '\u09ec', '\u09ed', '\u09ee'
, '\u09ef',
// Kannada
'\u0ce6', '\u0ce7', '\u0ce8', '\u0ce9', '\u0cea', '\u0ceb', '\u0cec', '\u0ced', '\u0cee'
, '\u0cef'
};
// XXX This doesn't allow for range limits when controlled by a
// soft input method!
public int getInputType() {
return InputType.TYPE_CLASS_TEXT;
}
#Override
protected char[] getAcceptedChars() {
return DIGIT_CHARACTERS;
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
Log.v("filter", "source:" + source.toString());
CharSequence filtered = String.valueOf(source.subSequence(start, end));
Log.v("filter", "filtered:" + filtered.toString());
if (TextUtils.isEmpty(filtered)) {
return "";
}
String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
+ dest.subSequence(dend, dest.length());
String str = String.valueOf(result).toLowerCase();
try{
int value = Integer.parseInt(str);
if(1 <= value && value <= 12) {
return source;
}
} catch(NumberFormatException e) {
//continue with the checking
}
for (String val : mMonthPicker.getDisplayedValues()) {
String valLowerCase = val.toLowerCase();
if (valLowerCase.startsWith(str)) {
final int selstart = result.length();
final int selend = val.length();
mInputText.post(new Runnable() {
#Override
public void run() {
mInputText.setSelection(selstart, selend);
}
});
return val.subSequence(dstart, val.length());
}
}
return "";
}
}
and this is the code to find the EditText:
private EditText findInput(ViewGroup np) {
int count = np.getChildCount();
for (int i = 0; i < count; i++) {
final View child = np.getChildAt(i);
if (child instanceof ViewGroup) {
findInput((ViewGroup) child);
} else if (child instanceof EditText) {
return (EditText) child;
}
}
return null;
}

Just to elaborate onto this, here is what I did to get the EditText input value (expanding on SnyersK code). The custom edit text can also be set to disable keyboard input and have a min and max value in xml:
xml use :
<com.octane.smartlink.widgets.SmartLinkNumberPicker
android:id="#+id/distance_tenths_of_distance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
max="9"
min="0"/>
Android widget class:
public class SmartLinkNumberPicker extends NumberPicker {
//declare needed variables
private EditText numberEditText;
private boolean shouldDisableEditText = true; //set to true to disable edit text input and focus
public SmartLinkNumberPicker(Context context) {
super(context);
}
public SmartLinkNumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public SmartLinkNumberPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(attrs);
}
private void init(AttributeSet attrs) {
numberEditText = populateEditText((ViewGroup) this);
if(numberEditText != null && shouldDisableEditText)
numberEditText.setFocusable(false);
processAttributeSet(attrs);
}
private EditText populateEditText(ViewGroup viewGroup) {
int count = viewGroup.getChildCount();
for(int i = 0; i < count; i++) {
final View child = viewGroup.getChildAt(i);
if (child instanceof ViewGroup)
populateEditText((ViewGroup) child);
else if (child instanceof EditText)
return (EditText) child;
}
return null;
}
/*
* used to set min, max attributes in xml
* set by min = "0" and max = "9"
*/
private void processAttributeSet(AttributeSet attrs) {
this.setMinValue(attrs.getAttributeIntValue(null, "min", 0));
this.setMaxValue(attrs.getAttributeIntValue(null, "max", 0));
}
/*
* always returns a valid number since number input is only allowed
* this will capture value set by either plus/minus buttons or edit text input
*/
#Override
public int getValue() {
return Integer.valueOf(numberEditText.getEditableText().toString());
}
}

Related

Andorid TextView, Linkify US phone number in every language

I have a text containing us phone numbers. I would like to make them clickable whatever is the phone language. I investigated how the autolink worked and found the Linkify.addLinks method I tried to use on a custom TextView.
public class PhoneNumberLinkTextView extends android.support.v7.widget.AppCompatTextView {
public PhoneNumberLinkTextView(Context context) {
super(context);
}
public PhoneNumberLinkTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public PhoneNumberLinkTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public void setUSNumberText(CharSequence text) {
SpannableStringBuilder spanText = new SpannableStringBuilder(text);
if (addLinks(spanText)) {
setText(spanText);
} else {
setText(text);
}
}
public boolean addLinks(#NonNull SpannableStringBuilder text) {
ArrayList<LinkSpec> links = new ArrayList<>();
gatherTelLinks(links, text);
if (links.isEmpty()) {
return false;
}
Object[] spans = text.getSpans(0, text.length(), Object.class);
final int count = spans.length;
for (int i = 0; i < count; i++) {
text.removeSpan(spans[i]);
}
for (LinkSpec link: links) {
applyLink(link.url, link.start, link.end, text);
}
return true;
}
private void gatherTelLinks(ArrayList<LinkSpec> links, Spannable s) {
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(),
Locale.US.getCountry(), PhoneNumberUtil.Leniency.POSSIBLE, Long.MAX_VALUE);
for (PhoneNumberMatch match : matches) {
LinkSpec spec = new LinkSpec();
spec.url = "tel:" + normalizeNumber(match.rawString());
spec.start = match.start();
spec.end = match.end();
links.add(spec);
}
}
private void applyLink(String url, int start, int end, Spannable text) {
URLSpan span = new URLSpan (url);
text.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
/**
* Normalize a phone number by removing the characters other than digits. If
* the given number has keypad letters, the letters will be converted to
* digits first.
*
* #param phoneNumber the number to be normalized.
* #return the normalized number.
*/
public String normalizeNumber(String phoneNumber) {
if (TextUtils.isEmpty(phoneNumber)) {
return "";
}
StringBuilder sb = new StringBuilder();
int len = phoneNumber.length();
for (int i = 0; i < len; i++) {
char c = phoneNumber.charAt(i);
// Character.digit() supports ASCII and Unicode digits (fullwidth, Arabic-Indic, etc.)
int digit = Character.digit(c, 10);
if (digit != -1) {
sb.append(digit);
} else if (sb.length() == 0 && c == '+') {
sb.append(c);
} else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
return normalizeNumber(PhoneNumberUtils.convertKeypadLettersToDigits(phoneNumber));
}
}
return sb.toString();
}
class LinkSpec {
String url;
int start;
int end;
}
}
This code is currently visually working. My US number is formatted as I expect it to be but my phone number is not clickable.
I then tried to add a setMovementMethod(LinkMovementMethod.getInstance()) after my setText() but this time I lost my US number formatted as a phone number.
Does anyone know how I can achieve what I'm trying to do ?
I ended solving my problem by replacing URLSpan by a custom class extending ClickableSpan.
private class USNumberSpan extends ClickableSpan {
private String url;
USNumberSpan(String url) {
this.url = url;
}
#Override
public void onClick(View widget) {
Uri uri = Uri.parse(url);
Context context = widget.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
try {
context.startActivity(intent);
} catch (ActivityNotFoundException e) {
Log.w("URLSpan", "Activity was not found for intent, " + intent.toString(), e);
}
}
}
I did not find out why this is working whereas URLSpan is not but I'm glad it worked.
I've found a Kotlin answer (by the user Iliya Mashin) with a Pattern for any type of numbers on this link: android:autoLink for phone numbers doesn't always work
I adapted it for Java and specified at least 4 numbers in the end (so it won't linkify some zipcodes ending with 3 numbers "xxxxx-xxx"), so, if you don't want this specific limitation, just remove the "{4,}" in the end of the expression).
LinkifyCompat.addLinks(textView, Linkify.ALL); // This will use the usual linkify for any other format
Pattern pattern = Pattern.compile("([\\d|\\(][\\h|\\(\\d{3}\\)|\\.|\\-|\\d]{4,}\\d{4,})", Pattern.CASE_INSENSITIVE);
LinkifyCompat.addLinks(textView, pattern, "tel://", null, null, null); // this adds the format for all kinds of phone number
If you want to link just the numbers, remove the first line (the one with "Linkify.ALL").

Using PhoneNumberFormattingTextWatcher without typing country calling code

In the login panel of my app, I divided the country calling code and the remaining numbers in two editable TextView as below:
I want to use international formatting standard in the TextView on the right. If a user who has a phone number as +905444444444 types in number in these boxes, I want to see "90" in the box on the left and "544 444 4444" on the right.
For this reason, I tried to use the following implementation that uses libphonenumber:
/**
* Watches a {#link android.widget.TextView} and if a phone number is entered
* will format it.
* <p>
* Stop formatting when the user
* <ul>
* <li>Inputs non-dialable characters</li>
* <li>Removes the separator in the middle of string.</li>
* </ul>
* <p>
* The formatting will be restarted once the text is cleared.
*/
public class PhoneNumberFormattingTextWatcher implements TextWatcher {
/**
* Indicates the change was caused by ourselves.
*/
private boolean mSelfChange = false;
/**
* Indicates the formatting has been stopped.
*/
private boolean mStopFormatting;
private AsYouTypeFormatter mFormatter;
private String code;
/**
* The formatting is based on the current system locale and future locale changes
* may not take effect on this instance.
*/
public PhoneNumberFormattingTextWatcher() {
this(Locale.getDefault().getCountry());
}
/**
* The formatting is based on the given <code>countryCode</code>.
*
* #param countryCode the ISO 3166-1 two-letter country code that indicates the country/region
* where the phone number is being entered.
*/
public PhoneNumberFormattingTextWatcher(String countryCode) {
if (countryCode == null) throw new IllegalArgumentException();
mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
if (mSelfChange || mStopFormatting) {
return;
}
// If the user manually deleted any non-dialable characters, stop formatting
if (count > 0 && hasSeparator(s, start, count)) {
stopFormatting();
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mSelfChange || mStopFormatting) {
return;
}
// If the user inserted any non-dialable characters, stop formatting
if (count > 0 && hasSeparator(s, start, count)) {
stopFormatting();
}
}
#Override
public synchronized void afterTextChanged(Editable s) {
if (mStopFormatting) {
// Restart the formatting when all texts were clear.
mStopFormatting = !(s.length() == 0);
return;
}
if (mSelfChange) {
// Ignore the change caused by s.replace().
return;
}
String formatted = reformat(s, Selection.getSelectionEnd(s));
if (formatted != null) {
int rememberedPos = mFormatter.getRememberedPosition();
mSelfChange = true;
s.replace(0, s.length(), formatted, 0, formatted.length());
// The text could be changed by other TextWatcher after we changed it. If we found the
// text is not the one we were expecting, just give up calling setSelection().
if (formatted.equals(s.toString())) {
Selection.setSelection(s, rememberedPos);
}
mSelfChange = false;
}
// PhoneNumberUtils.ttsSpanAsPhoneNumber(s, 0, s.length());
}
/**
* Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the
* nearest dialable char to the left. For instance, if the number is (650) 123-45678 and '4' is
* removed then the cursor should be behind '3' instead of '-'.
*/
private String reformat(CharSequence s, int cursor) {
// The index of char to the leftward of the cursor.
int curIndex = cursor - 1;
String formatted = null;
mFormatter.clear();
char lastNonSeparator = 0;
boolean hasCursor = false;
int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (PhoneNumberUtils.isNonSeparator(c)) {
if (lastNonSeparator != 0) {
formatted = getFormattedNumber(lastNonSeparator, hasCursor);
hasCursor = false;
}
lastNonSeparator = c;
}
if (i == curIndex) {
hasCursor = true;
}
}
if (lastNonSeparator != 0) {
formatted = getFormattedNumber(lastNonSeparator, hasCursor);
}
return formatted;
}
private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) {
return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator)
: mFormatter.inputDigit(lastNonSeparator);
}
private void stopFormatting() {
mStopFormatting = true;
mFormatter.clear();
}
private boolean hasSeparator(final CharSequence s, final int start, final int count) {
for (int i = start; i < start + count; i++) {
char c = s.charAt(i);
if (!PhoneNumberUtils.isNonSeparator(c)) {
return true;
}
}
return false;
}
}
However, this TextWatcher formats the numbers includes the calling code. In other words, it successfully formats "+905444444444" but cannot format "54444444444". How can I achieve to get the same result when the input phone number includes the country code in the TextView on the right? Needless to say but I want to get the following output:
5
54
544
544 4
544 44
544 444
544 444 4
544 444 44 ...
I edited reformat(charSequence, cursor) method and achieved to get the internationally formatted phone numbers without country calling code at last. If you want to get the same result, you can see the edited code below:
/**
* Watches a {#link android.widget.TextView} and if a phone number is entered
* will format it.
* <p>
* Stop formatting when the user
* <ul>
* <li>Inputs non-dialable characters</li>
* <li>Removes the separator in the middle of string.</li>
* </ul>
* <p>
* The formatting will be restarted once the text is cleared.
*/
public class PhoneNumberFormattingTextWatcher implements TextWatcher {
/**
* Indicates the change was caused by ourselves.
*/
private boolean mSelfChange = false;
/**
* Indicates the formatting has been stopped.
*/
private boolean mStopFormatting;
private AsYouTypeFormatter mFormatter;
private String countryCode;
/**
* The formatting is based on the current system locale and future locale changes
* may not take effect on this instance.
*/
public PhoneNumberFormattingTextWatcher() {
this(Locale.getDefault().getCountry());
}
/**
* The formatting is based on the given <code>countryCode</code>.
*
* #param countryCode the ISO 3166-1 two-letter country code that indicates the country/region
* where the phone number is being entered.
*
* #hide
*/
public PhoneNumberFormattingTextWatcher(String countryCode) {
if (countryCode == null) throw new IllegalArgumentException();
mFormatter = PhoneNumberUtil.getInstance().getAsYouTypeFormatter(countryCode);
this.countryCode = countryCode;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
if (mSelfChange || mStopFormatting) {
return;
}
// If the user manually deleted any non-dialable characters, stop formatting
if (count > 0 && hasSeparator(s, start, count)) {
stopFormatting();
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mSelfChange || mStopFormatting) {
return;
}
// If the user inserted any non-dialable characters, stop formatting
if (count > 0 && hasSeparator(s, start, count)) {
stopFormatting();
}
}
#Override
public synchronized void afterTextChanged(Editable s) {
if (mStopFormatting) {
// Restart the formatting when all texts were clear.
mStopFormatting = !(s.length() == 0);
return;
}
if (mSelfChange) {
// Ignore the change caused by s.replace().
return;
}
String formatted = reformat(s, Selection.getSelectionEnd(s));
if (formatted != null) {
int rememberedPos = formatted.length();
Log.v("rememberedPos", "" + rememberedPos);
mSelfChange = true;
s.replace(0, s.length(), formatted, 0, formatted.length());
// The text could be changed by other TextWatcher after we changed it. If we found the
// text is not the one we were expecting, just give up calling setSelection().
if (formatted.equals(s.toString())) {
Selection.setSelection(s, rememberedPos);
}
mSelfChange = false;
}
}
/**
* Generate the formatted number by ignoring all non-dialable chars and stick the cursor to the
* nearest dialable char to the left. For instance, if the number is (650) 123-45678 and '4' is
* removed then the cursor should be behind '3' instead of '-'.
*/
private String reformat(CharSequence s, int cursor) {
// The index of char to the leftward of the cursor.
int curIndex = cursor - 1;
String formatted = null;
mFormatter.clear();
char lastNonSeparator = 0;
boolean hasCursor = false;
String countryCallingCode = "+" + CountryCodesAdapter.getCode(countryCode);
s = countryCallingCode + s;
int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (PhoneNumberUtils.isNonSeparator(c)) {
if (lastNonSeparator != 0) {
formatted = getFormattedNumber(lastNonSeparator, hasCursor);
hasCursor = false;
}
lastNonSeparator = c;
}
if (i == curIndex) {
hasCursor = true;
}
}
if (lastNonSeparator != 0) {
Log.v("lastNonSeparator", "" + lastNonSeparator);
formatted = getFormattedNumber(lastNonSeparator, hasCursor);
}
if (formatted.length() > countryCallingCode.length()) {
if (formatted.charAt(countryCallingCode.length()) == ' ')
return formatted.substring(countryCallingCode.length() + 1);
return formatted.substring(countryCallingCode.length());
}
return formatted.substring(formatted.length());
}
private String getFormattedNumber(char lastNonSeparator, boolean hasCursor) {
return hasCursor ? mFormatter.inputDigitAndRememberPosition(lastNonSeparator)
: mFormatter.inputDigit(lastNonSeparator);
}
private void stopFormatting() {
mStopFormatting = true;
mFormatter.clear();
}
private boolean hasSeparator(final CharSequence s, final int start, final int count) {
for (int i = start; i < start + count; i++) {
char c = s.charAt(i);
if (!PhoneNumberUtils.isNonSeparator(c)) {
return true;
}
}
return false;
}
}
Thank you #Dorukhan Arslan and #NixSam for the answers. The accepted answer is working well but the problem occurs when user changes the digit somewhere in middle. The other answer helps there, but for some edge case, it was not behaving as I wanted. So I thought to solve it in a different way. This solution uses "digitsBeforeCursor" to maintain the correct cursor position every time [hopefully:-)].
For all those who are facing the problem, there are two options for you to solve this.
1. Easy and Ready to GO option
If you are planning to take international phone input, you can use CCP Library which can give you total power for the full international number with ease and flexibility. It will allow you to do something like this. It will handle formatting along with the country selector (bonus).
2. Custom option
If you want to implement things from the scratch here you go.
Add Optimized Android port of libphonenumber by Michael Rozumyanskiy to your project by adding following in your gradle file.
dependencies {
compile 'io.michaelrocks:libphonenumber-android:8.9.0'
}
Create a new class named InternationalPhoneTextWatcher
Add following code to that class. CCP uses this class here. Then use object of this class to the editText. This will take country name code and phone code in constructor. and will update formatting automatically when updateCountry() is called to change the country.
public class InternationalPhoneTextWatcher implements TextWatcher {
// Reference https://stackoverflow.com/questions/32661363/using-phonenumberformattingtextwatcher-without-typing-country-calling-code to solve formatting issue
// Check parent project of this class at https://github.com/hbb20/CountryCodePickerProject
private static final String TAG = "Int'l Phone TextWatcher";
PhoneNumberUtil phoneNumberUtil;
/**
* Indicates the change was caused by ourselves.
*/
private boolean mSelfChange = false;
/**
* Indicates the formatting has been stopped.
*/
private boolean mStopFormatting;
private AsYouTypeFormatter mFormatter;
private String countryNameCode;
Editable lastFormatted = null;
private int countryPhoneCode;
//when country is changed, we update the number.
//at this point this will avoid "stopFormatting"
private boolean needUpdateForCountryChange = false;
/**
* #param context
* #param countryNameCode ISO 3166-1 two-letter country code that indicates the country/region
* where the phone number is being entered.
* #param countryPhoneCode Phone code of country. https://countrycode.org/
*/
public InternationalPhoneTextWatcher(Context context, String countryNameCode, int countryPhoneCode) {
if (countryNameCode == null || countryNameCode.length() == 0)
throw new IllegalArgumentException();
phoneNumberUtil = PhoneNumberUtil.createInstance(context);
updateCountry(countryNameCode, countryPhoneCode);
}
public void updateCountry(String countryNameCode, int countryPhoneCode) {
this.countryNameCode = countryNameCode;
this.countryPhoneCode = countryPhoneCode;
mFormatter = phoneNumberUtil.getAsYouTypeFormatter(countryNameCode);
mFormatter.clear();
if (lastFormatted != null) {
needUpdateForCountryChange = true;
String onlyDigits = phoneNumberUtil.normalizeDigitsOnly(lastFormatted);
lastFormatted.replace(0, lastFormatted.length(), onlyDigits, 0, onlyDigits.length());
needUpdateForCountryChange = false;
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
if (mSelfChange || mStopFormatting) {
return;
}
// If the user manually deleted any non-dialable characters, stop formatting
if (count > 0 && hasSeparator(s, start, count) && !needUpdateForCountryChange) {
stopFormatting();
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (mSelfChange || mStopFormatting) {
return;
}
// If the user inserted any non-dialable characters, stop formatting
if (count > 0 && hasSeparator(s, start, count)) {
stopFormatting();
}
}
#Override
public synchronized void afterTextChanged(Editable s) {
if (mStopFormatting) {
// Restart the formatting when all texts were clear.
mStopFormatting = !(s.length() == 0);
return;
}
if (mSelfChange) {
// Ignore the change caused by s.replace().
return;
}
//calculate few things that will be helpful later
int selectionEnd = Selection.getSelectionEnd(s);
boolean isCursorAtEnd = (selectionEnd == s.length());
//get formatted text for this number
String formatted = reformat(s);
//now calculate cursor position in formatted text
int finalCursorPosition = 0;
if (formatted.equals(s.toString())) {
//means there is no change while formatting don't move cursor
finalCursorPosition = selectionEnd;
} else if (isCursorAtEnd) {
//if cursor was already at the end, put it at the end.
finalCursorPosition = formatted.length();
} else {
// if no earlier case matched, we will use "digitBeforeCursor" way to figure out the cursor position
int digitsBeforeCursor = 0;
for (int i = 0; i < s.length(); i++) {
if (i >= selectionEnd) {
break;
}
if (PhoneNumberUtils.isNonSeparator(s.charAt(i))) {
digitsBeforeCursor++;
}
}
//at this point we will have digitsBeforeCursor calculated.
// now find this position in formatted text
for (int i = 0, digitPassed = 0; i < formatted.length(); i++) {
if (digitPassed == digitsBeforeCursor) {
finalCursorPosition = i;
break;
}
if (PhoneNumberUtils.isNonSeparator(formatted.charAt(i))) {
digitPassed++;
}
}
}
//if this ends right before separator, we might wish to move it further so user do not delete separator by mistake.
// because deletion of separator will cause stop formatting that should not happen by mistake
if (!isCursorAtEnd) {
while (0 < finalCursorPosition - 1 && !PhoneNumberUtils.isNonSeparator(formatted.charAt(finalCursorPosition - 1))) {
finalCursorPosition--;
}
}
//Now we have everything calculated, set this values in
if (formatted != null) {
mSelfChange = true;
s.replace(0, s.length(), formatted, 0, formatted.length());
mSelfChange = false;
lastFormatted = s;
Selection.setSelection(s, finalCursorPosition);
}
}
/**
* this will format the number in international format (only).
*/
private String reformat(CharSequence s) {
String internationalFormatted = "";
mFormatter.clear();
char lastNonSeparator = 0;
String countryCallingCode = "+" + countryPhoneCode;
//to have number formatted as international format, add country code before that
s = countryCallingCode + s;
int len = s.length();
for (int i = 0; i < len; i++) {
char c = s.charAt(i);
if (PhoneNumberUtils.isNonSeparator(c)) {
if (lastNonSeparator != 0) {
internationalFormatted = mFormatter.inputDigit(lastNonSeparator);
}
lastNonSeparator = c;
}
}
if (lastNonSeparator != 0) {
internationalFormatted = mFormatter.inputDigit(lastNonSeparator);
}
internationalFormatted = internationalFormatted.trim();
if (internationalFormatted.length() > countryCallingCode.length()) {
if (internationalFormatted.charAt(countryCallingCode.length()) == ' ')
internationalFormatted = internationalFormatted.substring(countryCallingCode.length() + 1);
else
internationalFormatted = internationalFormatted.substring(countryCallingCode.length());
} else {
internationalFormatted = "";
}
return TextUtils.isEmpty(internationalFormatted) ? "" : internationalFormatted;
}
private void stopFormatting() {
mStopFormatting = true;
mFormatter.clear();
}
private boolean hasSeparator(final CharSequence s, final int start, final int count) {
for (int i = start; i < start + count; i++) {
char c = s.charAt(i);
if (!PhoneNumberUtils.isNonSeparator(c)) {
return true;
}
}
return false;
}
}
Complete example of CCP Library:
Layout:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<com.hbb20.CountryCodePicker
android:id="#+id/ccp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:ccp_textSize="20sp"
android:layout_gravity="center"
app:ccp_flagBorderColor="#color/colorPrimary"
/>
<EditText
android:id="#+id/phone"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:textSize="20sp"
android:autofillHints="Enter phone number"
android:inputType="phone|numberDecimal"
android:hint="#string/your_phone"
tools:text="9000000000"
/>
</LinearLayout>
Activity/Fragment (In my case - fragment):
package app.my.fragments;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.hbb20.CountryCodePicker;
import com.hbb20.InternationalPhoneTextWatcher;
import java.util.Locale;
import app.my.R;
import app.my.util.Logger;
import app.my.util.TextHelper;
public class LoginEnterPhoneFragment extends Fragment {
private final static String TAG = LoginEnterPhoneFragment.class.getSimpleName();
private EditText phoneNumberView;
private CountryCodePicker ccp;
private InternationalPhoneTextWatcher internationalPhoneTextWatcher;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_login_phone, container, false);
phoneNumberView = view.findViewById(R.id.phone);
ccp = view.findViewById(R.id.ccp);
// Setting up ccp
ccp.setDefaultCountryUsingNameCode(Locale.getDefault().getCountry());
ccp.showNameCode(false);
ccp.setOnCountryChangeListener(new CountryCodePicker.OnCountryChangeListener() {
#Override
public void onCountrySelected() {
if (internationalPhoneTextWatcher != null) {
phoneNumberView.removeTextChangedListener(internationalPhoneTextWatcher);
}
internationalPhoneTextWatcher = new InternationalPhoneTextWatcher(getContext(), ccp.getSelectedCountryNameCode(), ccp.getSelectedCountryCodeAsInt());
phoneNumberView.addTextChangedListener(internationalPhoneTextWatcher);
// Triggering phoneNumberView.TextChanged to reformat phone number
if (TextHelper.isNotEmpty(phoneNumberView.getText().toString())) {
phoneNumberView.setText(String.format("+%s", phoneNumberView.getText()));
}
}
});
// Triggering ccp.CountryChanged to add InternationalPhoneTextWatcher to phoneNumberView
ccp.setCountryForNameCode(Locale.getDefault().getCountry());
// Setting up phoneNumberView
phoneNumberView.addTextChangedListener(new TextWatcher() {
#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 original = s.toString().replaceAll("[^\\d+]", "");
String result = original;
if (result.startsWith(ccp.getDefaultCountryCodeWithPlus())) {
result = result.substring(ccp.getDefaultCountryCodeWithPlus().length());
}
if (result.startsWith("+")) {
result = result.substring(1);
}
if (!original.equals(result)) {
phoneNumberView.setText(result);
}
}
});
return view;
}
}
Works OK but... Cursor is not set on proper position. When user change cursor inside edit text and enter number, cursor goes to the end. I've added class holding formatted number and position and return it from reformat method.
return new InputFormatted(TextUtils.isEmpty(formatted) ? "" : formatted,
mFormatter.getRememberedPosition());
After that only set
Selection.setSelection(s, formatted.getPosition());

EditText selection on focus not working

Since I am very disappointed in a way that Android is doing decimal input I decided to write my own EditDecimal control that inherits from EditText. I want it to select all on click and to put cursor on first position on focus. I don't want cursor to show because it's usless on most Android phones (you mostly cannot put it in right place and it makes me very nervous)
Problem is when you tap on control - FocusChanged is called but it does not set position of (hidden) cursor on first position but on position where user tapped. I can not find the problem... is there some other event that happens after FocusChange that moves cursor?
public class EditDecimal : EditText
{
// Every constructor is calling Initialize ...
private void Initialize()
{
FocusChange += OnFocusChanged;
Click += OnClicked;
SetCursorVisible(false);
}
private void OnFocusChanged(object sender, FocusChangeEventArgs e)
{
if (IsFocused)
SetSelection(1);
}
private void OnClicked(object sender, EventArgs e)
{
SelectAll();
}
}
I finnaly solved it. I had to subscribe to OnTouch event and to change cursor position from there.
public class EditDecimal : EditText
{
// Every constructor is calling Initialize ...
private InputMethodManager _imm ;
public int DecimalSpaces { get; set; }
readonly DecimalFormatSymbols _dfs = new DecimalFormatSymbols();
private void Initialize(Context context, IAttributeSet attrs)
{
AfterTextChanged += OnAfterTextChanged;
SetSelectAllOnFocus(true);
SetCursorVisible(false);
Touch += OnTouch;
_imm = (InputMethodManager)context.GetSystemService(Context.InputMethodService);
var a = context.ObtainStyledAttributes(attrs, Resource.Styleable.EditDecimal);
try
{
DecimalSpaces = a.GetInteger(Resource.Styleable.EditDecimal_decimalSpaces, 2);
}
finally
{
a.Recycle();
}
}
private void OnTouch(object sender, TouchEventArgs e)
{
base.OnTouchEvent(e.Event);
if (e.Event.Action == MotionEventActions.Up)
{
SelectAll();
}
}
protected override void OnFocusChanged(bool gainFocus, FocusSearchDirection direction, Rect previouslyFocusedRect)
{
base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect);
SelectAll();
}
private void OnAfterTextChanged(object sender, AfterTextChangedEventArgs e)
{
const char ch1 = '.';
const char ch2 = ',';
switch (_dfs.DecimalSeparator)
{
case ch2:
{
if (Text.Contains(ch1.ToString()))
{
var position = Text.IndexOf(ch1);
if (Text.Contains(ch2.ToString()))
e.Editable.Delete(position, position + 1);
else
e.Editable.Replace(position, position + 1, ch2.ToString());
}
// we have to prevent showing two commas in number
var firstCommaPosition = Text.IndexOf(ch2);
var lastCommaPosition = Text.LastIndexOf(ch2);
if (firstCommaPosition > 0 && lastCommaPosition > 0 && firstCommaPosition != lastCommaPosition)
e.Editable.Delete(lastCommaPosition, lastCommaPosition+1);
}
break;
case ch1:
{
if (Text.Contains(ch2.ToString()))
{
var position = Text.IndexOf(ch2);
e.Editable.Delete(position, position + 1);
}
// we have to prevent showing two points in number
var firstPointPosition = Text.IndexOf(ch1);
var lastPointPosition = Text.LastIndexOf(ch1);
if (firstPointPosition > 0 && lastPointPosition > 0 && firstPointPosition != lastPointPosition)
e.Editable.Delete(lastPointPosition, lastPointPosition + 1);
}
break;
}
//thnx to http://stackoverflow.com/users/2240673/tom
var length = e.Editable.Length();
if (length <= 0) return;
if (NrOfDecimal(e.Editable.ToString()) > DecimalSpaces)
e.Editable.Delete(length - 1, length);
}
private int NrOfDecimal(string nr)
{
if (nr == null) return 0;
var nrCharArray = nr.ToCharArray();
var len = nr.Length;
var pos = len;
for (var i = 0; i < len; i++)
{
if (nrCharArray[i] != '.') continue;
pos = i + 1;
break;
}
return len - pos;
}
}
What came as suprize to me was a fact that EditText selection behaves differently if SetCursorVisible is set to true than when it is set to false. I thought that it was just visibility property.
Grepcode is your friend. It shows that TextView's onFocusChanged method checks if there's an Editor object defined for the View (which is the case with EditText) and a look at the Editor code shows this at line 889 ff:
// If a tap was used to give focus to that view, move cursor at tap position.
// Has to be done before onTakeFocus, which can be overloaded.
final int lastTapPosition = getLastTapPosition();
if (lastTapPosition >= 0) {
Selection.setSelection((Spannable) mTextView.getText(), lastTapPosition);
}
So perhaps the best way would be to write your own implementation of Editor. Although if have no idea right now how you attach that to your TextView resp. EditText. class.
Or you just set the selection back to the first position if that is what you want.

custom Edit text Android

i have 2 android project.
first project--> custom edit text that i made with custom regular expresion like this
private static final String QUANTITY_REGEX = "^\\d{0,4}(\\,\\d{0,3})?$";
second project --> project that use custom edit text from first project.
after that, i exported the first class to be a library on second project as a custom edittext.
the problem is :
as you can see the regular expression on first project only allowing 4 number to be written first, and 2 digit after "," symbol on edit text. but i want to make the custom edit text to be like this
isFocused: 1234,56
!isFocused : 1.234,56
how to make it possible. thx
Change your regex to ^\d{0,4}(.\d{0,4})?(,\d{0,2})?$
private static final String QUANTITY_REGEX ="^\\d{0,4}(.\\d{0,4})?(,\\d{0,2})?$";
The matches will be
1234
1234.5678
1234.5678,12
here what i`ve figure out... hope can usefull for other programmer
the concept :
1 edit text
when user focused on edittext (txt1), user only can write 4 number , a comma (",") and 3 another number after comma (",") --> 1234,567
when (txt1) onFocused = false (user click to another editText) txt1 show the number like this 1.234,567
here the code
VALIDATION CLASS
public class Validation {
// Regular Expression
// you can change the expression based on your need
private static final String EMAIL_REGEX = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*#[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
private static final String PHONE_REGEX = "\\d{3}-\\d{7}";
private static final String CURRENCY_REGEX = "^\\d{0,8}(\\,\\d{0,2})?$";
public String maxLength = Quantity.maxLengthFront;
public String QUANTITY_REGEX = null;
// Error Messages
private static final String REQUIRED_MSG = "required";
private static final String EMAIL_MSG = "invalid email";
private static final String PHONE_MSG = "###-#######";
private static final String CURRENCY_MSG = "invalid currency";
private static final String QUANTITY_MSG = "invalid quantity";
// call this method when you need to check email validation
public static boolean isEmailAddress(EditText editText, boolean required) {
return isValid(editText, EMAIL_REGEX, EMAIL_MSG, required);
}
// call this method when you need to check phone number validation
public static boolean isPhoneNumber(EditText editText, boolean required) {
return isValid(editText, PHONE_REGEX, PHONE_MSG, required);
}
// call this method when you need to check currency validation
public static boolean isCurrency(EditText editText, boolean required) {
return isValid(editText, CURRENCY_REGEX, CURRENCY_MSG, required);
}
public boolean isQuantity(EditText editText, boolean required){
return isValid(editText, QUANTITY_REGEX, QUANTITY_MSG, required);
}
// return true if the input field is valid, based on the parameter passed
public static boolean isValid(EditText editText, String regex, String errMsg, boolean required) {
String text = editText.getText().toString().trim();
// clearing the error, if it was previously set by some other values
editText.setError(null);
// text required and editText is blank, so return false
if ( required && !hasText(editText) ) return false;
// pattern doesn't match so returning false
if (required && !Pattern.matches(regex, text)) {
editText.setError(errMsg);
return false;
};
// pattern doesn't match so returning false
if (!required && !Pattern.matches(regex, text)) {
editText.setError(errMsg);
return false;
};
return true;
}
// check the input field has any text or not
// return true if it contains text otherwise false
public static boolean hasText(EditText editText) {
String text = editText.getText().toString().trim();
editText.setError(null);
// length 0 means there is no text
if (text.length() == 0) {
editText.setError(REQUIRED_MSG);
return false;
}
return true;
}
}
QUANTITY CLASS
public class Quantity extends EditText {
public static String maxLength;
public static String maxLengthFront = "4";
public static String regexCustom;
public boolean statusFocused = false;
public boolean allowBlank;
public String decimalPlaces;
String oldText;
Validation validation = new Validation();
public Quantity(Context context) {
super(context);
}
public Quantity(Context context, AttributeSet attrs) {
super(context, attrs);
// --- Additional custom code --
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.Quantity);
final int N = a.getIndexCount();
for (int i = 0; i < N; ++i) {
int attr = a.getIndex(i);
switch (attr) {
case R.styleable.Quantity_decimalPlaces_quantity:
decimalPlaces = a.getString(attr);
// ...do something with delimiter...
break;
case R.styleable.Quantity_maxLength_quantity:
maxLength = "5";
// ...do something with fancyText...
doSetMaxLength();
break;
case R.styleable.Quantity_allowBlank_quantity:
allowBlank = a.getBoolean(attr, false);
// ...do something with fancyText...
break;
}
}
a.recycle();
this.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL);
this.setKeyListener(DigitsKeyListener.getInstance("1234567890-.,"));
this.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
if (statusFocused == true) {
maxLengthFront = "4";
if (validation.isQuantity(Quantity.this, false)) {
// Toast.makeText(getContext(), "Validation True", Toast.LENGTH_SHORT).show();
} else {
// Toast.makeText(getContext(), "Validation False",Toast.LENGTH_SHORT).show();
Quantity.this.setText(oldText);
Quantity.this.setSelection(Quantity.this.getText().length(), Quantity.this.getText().length());
}
} else {
maxLengthFront = "5";
}
validation.QUANTITY_REGEX = "^\\d{0," + maxLengthFront + "}(\\,\\d{0,3})?$";
}
public void beforeTextChanged(CharSequence s, int start, int count,int after) {
oldText = s.toString();
// Toast.makeText(getContext(), "Before change : " + s, Toast.LENGTH_SHORT).show();
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
});
this.setOnFocusChangeListener(new OnFocusChangeListener() {
public void onFocusChange(View v, boolean gainFocus) {
// onFocus
String s;
if (gainFocus) {
maxLengthFront = "4";
validation.QUANTITY_REGEX = "^\\d{0," + maxLengthFront + "}(\\,\\d{0,3})?$";
s = Quantity.this.getText().toString().replace(".", "");
Quantity.this.setText(s);
statusFocused = true;
}
// onBlur
else {
maxLengthFront = "5";
validation.QUANTITY_REGEX = "^\\d{0," + maxLengthFront + "}(\\.\\d{0,3})?$";
Double number = Double.parseDouble(Quantity.this.getText().toString());
DecimalFormatSymbols symbol = DecimalFormatSymbols.getInstance();
symbol.setGroupingSeparator('.');
symbol.setDecimalSeparator(',');
DecimalFormat formatter = new DecimalFormat("###,###.###", symbol);
Quantity.this.setText(formatter.format(number));
statusFocused = false;
}
}
});
}
public Quantity(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// --- Additional custom code --
}
// #Override
// protected void onTextChanged(CharSequence text, int start,
// int lengthBefore, int lengthAfter) {
// // TODO Auto-generated method stub
// super.onTextChanged(text, start, lengthBefore, lengthAfter);
// Toast.makeText(getContext(), this.getText() + " - " + text.toString(),
// Toast.LENGTH_SHORT).show();
// }
public void doSetMaxLength() {
InputFilter[] FilterArray = new InputFilter[1];
FilterArray[0] = new InputFilter.LengthFilter(Integer.parseInt(maxLength, 10));
this.setFilters(FilterArray);
}
}

How to Automatically add thousand separators as number is input in EditText

Im creating a convertor application. I want to set the EditText so that when the user is inputting the number to be converted, a thousand separator (,) should be added automatically in realtime to the number once it increments by 3 figures: thousand, million, billion etc.
And when erased to below 4 figures the number goes back to normal.
Any help?
Even-though It's late. Intended for future visitors.
Fetures of the following codes
Puts thousand separator in EditText as it's text changes.
adds 0. Automatically when pressed period (.) At First.
Ignores 0 input at Beginning.
Just copy the following
Class named
NumberTextWatcherForThousand which implements TextWatcher
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import java.util.StringTokenizer;
/**
* Created by skb on 12/14/2015.
*/
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(".")){
editText.setText("0.");
}
if(value.startsWith("0") && !value.startsWith("0.")){
editText.setText("");
}
String str = editText.getText().toString().replaceAll(",", "");
if (!value.equals(""))
editText.setText(getDecimalFormattedString(str));
editText.setSelection(editText.getText().toString().length());
}
editText.addTextChangedListener(this);
return;
}
catch (Exception ex)
{
ex.printStackTrace();
editText.addTextChangedListener(this);
}
}
public static String getDecimalFormattedString(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++;
}
}
public static String trimCommaOfString(String string) {
// String returnString;
if(string.contains(",")){
return string.replace(",","");}
else {
return string;
}
}
}
Use This Class on your EditText as follows
editText.addTextChangedListener(new NumberTextWatcherForThousand(editText));
To get the input as plain Double Text
Use the trimCommaOfString method of the same class like this
NumberTextWatcherForThousand.trimCommaOfString(editText.getText().toString())
Git
You can use String.format() in a TextWatcher. The comma in the format specifier does the trick.
This does not work for floating point input. And be careful not to set an infinite loop with the TextWatcher.
public void afterTextChanged(Editable view) {
String s = null;
try {
// The comma in the format specifier does the trick
s = String.format("%,d", Long.parseLong(view.toString()));
} catch (NumberFormatException e) {
}
// Set s back to the view after temporarily removing the text change listener
}
public static String doubleToStringNoDecimal(double d) {
DecimalFormat formatter = (DecimalFormat) NumberFormat.getInstance(Locale.US);
formatter.applyPattern("#,###");
return formatter.format(d);
}
This sample app deconstructs formatting numbers clearly.
To summarize the link above, use a TextWatcher and in the afterTextChanged() method format the EditText view with the following logic:
#Override
public void afterTextChanged(Editable s) {
editText.removeTextChangedListener(this);
try {
String originalString = s.toString();
Long longval;
if (originalString.contains(",")) {
originalString = originalString.replaceAll(",", "");
}
longval = Long.parseLong(originalString);
DecimalFormat formatter = (DecimalFormat) NumberFormat.getInstance(Locale.US);
formatter.applyPattern("#,###,###,###");
String formattedString = formatter.format(longval);
//setting text after format to EditText
editText.setText(formattedString);
editText.setSelection(editText.getText().length());
} catch (NumberFormatException nfe) {
nfe.printStackTrace();
}
editText.addTextChangedListener(this);
}
I know i am very late to the party but it may be very useful for future users. My answer is an extension of Shree Krishna's answer.
Improvements:
Thousands separators and Decimal markers are locale aware i.e. they are used accordingly to the Locale of the device.
The cursor position doesn't change after deleting or adding elements in the middle also (In his answer cursor was reset to the end).
The overall quality of the code has been improved specially the getDecimalFormattedString method.
Code:
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import java.text.DecimalFormat;
/**
* Created by srv_twry on 4/12/17.
* Source: https://stackoverflow.com/a/34265406/137744
* The custom TextWatcher that automatically adds thousand separators in EditText.
*/
public class ThousandSeparatorTextWatcher implements TextWatcher {
private DecimalFormat df;
private EditText editText;
private static String thousandSeparator;
private static String decimalMarker;
private int cursorPosition;
public ThousandSeparatorTextWatcher(EditText editText) {
this.editText = editText;
df = new DecimalFormat("#,###.##");
df.setDecimalSeparatorAlwaysShown(true);
thousandSeparator = Character.toString(df.getDecimalFormatSymbols().getGroupingSeparator());
decimalMarker = Character.toString(df.getDecimalFormatSymbols().getDecimalSeparator());
}
#Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
cursorPosition = editText.getText().toString().length() - editText.getSelectionStart();
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
#Override
public void afterTextChanged(Editable s) {
try {
editText.removeTextChangedListener(this);
String value = editText.getText().toString();
if (value != null && !value.equals("")) {
if (value.startsWith(decimalMarker)) {
String text = "0" + decimalMarker;
editText.setText(text);
}
if (value.startsWith("0") && !value.startsWith("0" + decimalMarker)) {
int index = 0;
while (index < value.length() && value.charAt(index) == '0') {
index++;
}
String newValue = Character.toString(value.charAt(0));
if (index != 0) {
newValue = value.charAt(0) + value.substring(index);
}
editText.setText(newValue);
}
String str = editText.getText().toString().replaceAll(thousandSeparator, "");
if (!value.equals("")) {
editText.setText(getDecimalFormattedString(str));
}
editText.setSelection(editText.getText().toString().length());
}
//setting the cursor back to where it was
editText.setSelection(editText.getText().toString().length() - cursorPosition);
editText.addTextChangedListener(this);
} catch (Exception ex) {
ex.printStackTrace();
editText.addTextChangedListener(this);
}
}
private static String getDecimalFormattedString(String value) {
String[] splitValue = value.split("\\.");
String beforeDecimal = value;
String afterDecimal = null;
String finalResult = "";
if (splitValue.length == 2) {
beforeDecimal = splitValue[0];
afterDecimal = splitValue[1];
}
int count = 0;
for (int i = beforeDecimal.length() - 1; i >= 0 ; i--) {
finalResult = beforeDecimal.charAt(i) + finalResult;
count++;
if (count == 3 && i > 0) {
finalResult = thousandSeparator + finalResult;
count = 0;
}
}
if (afterDecimal != null) {
finalResult = finalResult + decimalMarker + afterDecimal;
}
return finalResult;
}
/*
* Returns the string after removing all the thousands separators.
* */
public static String getOriginalString(String string) {
return string.replace(thousandSeparator,"");
}
}
This solution has some advantage over other answers. For example, it keeps the user's cursor position even if they edit the beginning or middle of the number. Other solutions always jump the cursor to the end of the number. It handles decimals and whole numbers, as well as locales that use characters other than . for the decimal separator and , for the thousands grouping separator.
class SeparateThousands(val groupingSeparator: String, val decimalSeparator: String) : TextWatcher {
private var busy = false
override fun afterTextChanged(s: Editable?) {
if (s != null && !busy) {
busy = true
var place = 0
val decimalPointIndex = s.indexOf(decimalSeparator)
var i = if (decimalPointIndex == -1) {
s.length - 1
} else {
decimalPointIndex - 1
}
while (i >= 0) {
val c = s[i]
if (c == groupingSeparator[0] ) {
s.delete(i, i + 1)
} else {
if (place % 3 == 0 && place != 0) {
// insert a comma to the left of every 3rd digit (counting from right to
// left) unless it's the leftmost digit
s.insert(i + 1, groupingSeparator)
}
place++
}
i--
}
busy = false
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
}
Then in xml:
<EditText
android:id="#+id/myNumberField"
android:digits=",.0123456789"
android:inputType="numberDecimal"
.../>
And finally register the watcher:
findViewById(R.id.myNumberField).addTextChangedListener(
SeparateThousands(groupingSeparator, decimalSeparator))
To handle . vs , in different locales use groupingSeparator and decimalSeparator, which can come from DecimalFormatSymbols or localized strings.
I just wanted comma to be placed and this is working for me:
String.format("%,.2f", myValue);
Here is my ThousandNumberEditText class
public class ThousandNumberEditText extends android.support.v7.widget.AppCompatEditText {
// TODO: 14/09/2017 change it if you want
private static final int MAX_LENGTH = 20;
private static final int MAX_DECIMAL = 3;
public ThousandNumberEditText(Context context) {
this(context, null);
}
public ThousandNumberEditText(Context context, AttributeSet attrs) {
this(context, attrs, android.support.v7.appcompat.R.attr.editTextStyle);
}
public ThousandNumberEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
addTextChangedListener(new ThousandNumberTextWatcher(this));
setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
setFilters(new InputFilter[] { new InputFilter.LengthFilter(MAX_LENGTH) });
setHint("0"); // TODO: 14/09/2017 change it if you want
}
private static class ThousandNumberTextWatcher implements TextWatcher {
private EditText mEditText;
ThousandNumberTextWatcher(EditText editText) {
mEditText = editText;
}
#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) {
String originalString = editable.toString();
String cleanString = originalString.replaceAll("[,]", "");
if (cleanString.isEmpty()) {
return;
}
String formattedString = getFormatString(cleanString);
mEditText.removeTextChangedListener(this);
mEditText.setText(formattedString);
mEditText.setSelection(mEditText.getText().length());
mEditText.addTextChangedListener(this);
}
/**
* Return the format string
*/
private String getFormatString(String cleanString) {
if (cleanString.contains(".")) {
return formatDecimal(cleanString);
} else {
return formatInteger(cleanString);
}
}
private String formatInteger(String str) {
BigDecimal parsed = new BigDecimal(str);
DecimalFormat formatter;
formatter = new DecimalFormat("#,###");
return formatter.format(parsed);
}
private String formatDecimal(String str) {
if (str.equals(".")) {
return ".";
}
BigDecimal parsed = new BigDecimal(str);
DecimalFormat formatter;
formatter =
new DecimalFormat("#,###." + getDecimalPattern(str)); //example patter #,###.00
return formatter.format(parsed);
}
/**
* It will return suitable pattern for format decimal
* For example: 10.2 -> return 0 | 10.23 -> return 00 | 10.235 -> return 000
*/
private String getDecimalPattern(String str) {
int decimalCount = str.length() - 1 - str.indexOf(".");
StringBuilder decimalPattern = new StringBuilder();
for (int i = 0; i < decimalCount && i < MAX_DECIMAL; i++) {
decimalPattern.append("0");
}
return decimalPattern.toString();
}
}
}
Using
<.ThousandNumberEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
You can use this method:
myEditText.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) {
String input = s.toString();
if (!input.isEmpty()) {
input = input.replace(",", "");
DecimalFormat format = new DecimalFormat("#,###,###");
String newPrice = format.format(Double.parseDouble(input));
myEditText.removeTextChangedListener(this); //To Prevent from Infinite Loop
myEditText.setText(newPrice);
myEditText.setSelection(newPrice.length()); //Move Cursor to end of String
myEditText.addTextChangedListener(this);
}
}
#Override
public void afterTextChanged(final Editable s) {
}
});
And to get original text use this:
String input = myEditText.getText().toString();
input = input.replace(",", "");
Since i had the same problem i decided to find a solution to it
Find my function below i hope it helps people finding solution
securityDeposit.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
// TODO Auto-generated method stub
}
#Override
public void beforeTextChanged(CharSequence s, int start,
int before, int count) {
// TODO Auto-generated method stub
}
#Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
if (s.toString().trim().length() > 0) {
int rentValue = Integer.parseInt(s.toString()
.replaceAll(",", ""));
StringBuffer rentVal = new StringBuffer();
if (rentValue > 10000000) {
s.clear();
s.append("10,000,000");
} else {
if (s.length() == 4) {
char x[] = s.toString().toCharArray();
char y[] = new char[x.length + 1];
for (int z = 0; z < y.length; z++) {
if (z == 1) {
y[1] = ',';
} else {
if (z == 0)
y[z] = x[z];
else {
y[z] = x[z - 1];
}
}
}
for (int z = 0; z < y.length; z++) {
rentVal = rentVal.append(y[z]);
}
s.clear();
s.append(rentVal);
}
}
}
}
});
you can use this code in many ways in your program, you give it a string and it separate each three from right and place space there.
private String Spacer(String number){
StringBuilder strB = new StringBuilder();
strB.append(number);
int Three = 0;
for(int i=number.length();i>0;i--){
Three++;
if(Three == 3){
strB.insert(i-1, " ");
Three = 0;
}
}
return strB.toString();
}// end Spacer()
u can change it a bit and use it ontextchangelistener.
good luck
The answers here lack a method to handle actual user input, such as deleting characters or copying and pasting. This is an EditText field. If you want to add formatting in, you need to support editing that formatted value.
This implementation still has a deficiency depending on your use case. I didn't care about decimal values and assumed I would only be handling whole numbers. There's enough of how to handle that on this page and how to handle actual internationalization that I'll leave that as an exercise to the reader. If you need to do that, it shouldn't be too difficult to add "." to the regular expression to keep the decimal; you'll just have to be careful to acknowledge the numeral string still has a non numerical character.
This is designed to be used throughout multiple activities. New it once, give it your edit text and your data model and ignore it. The model binding can be removed if you don't need it.
public class EditNumberFormatter implements TextWatcher {
private EditText watched;
private Object model;
private Field field;
private IEditNumberFormatterListener listener;
private ActiveEdit activeEdit;
/**
* Binds an EditText to a data model field (Such as a room entity's public variable)
* Whenever the edit text is changed, the text is formatted to the local numerical format.
*
* Handles copy/paste/backspace/select&delete/typing
*
* #param model An object with a public field to bind to
* #param fieldName A field defined on the object
* #param watched The edit text to watch for changes
* #param listener Another object that wants to know after changes & formatting are done.
*/
public EditNumberFormatter(Object model, String fieldName, EditText watched, IEditNumberFormatterListener listener) {
this.model = model;
this.watched = watched;
this.listener = listener;
try {
field = model.getClass().getDeclaredField(fieldName);
} catch(Exception e) { }
watched.addTextChangedListener(this);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
activeEdit = new ActiveEdit(s.toString(), start, count);
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
activeEdit.recordChangedText(s.toString(),count);
}
#Override
public void afterTextChanged(Editable s) {
this.watched.removeTextChangedListener(this);
activeEdit.processEdit(); // Override the user's edit of the formatted string with what the user intended to do to the numeral.
watched.setText(activeEdit.getCurrentFormattedString());
watched.setSelection(activeEdit.getCursorPosition());
updateDataModel(activeEdit.getCurrentRawValue());
listener.FormatUpdated(watched.getId(), activeEdit.getCurrentRawValue(), activeEdit.getCurrentFormattedString());
this.watched.addTextChangedListener(this);
}
private void updateDataModel(int rawValue) {
try {
field.set(model, rawValue);
} catch (IllegalAccessException e) { }
}
/**
* Tracks the active editing of an EditText formatted for integer input
*/
private class ActiveEdit {
private String priorFormattedString;
private String currentFormattedString;
private String currentNumericalString;
private int currentRawValue;
private boolean removal;
private boolean addition;
private int changeStart;
private int removedCount;
private int additionCount;
private int numeralCountBeforeSelection;
private int numeralCountAdded;
private int numeralCountRemoved;
/**
* Call in beforeEdit to begin recording changes
*
* #param beforeEdit string before edit began
* #param start start position of edit
* #param removed number of characters removed
*/
public ActiveEdit(String beforeEdit, int start, int removed) {
removal = (removed > 0);
priorFormattedString = beforeEdit;
changeStart = start;
removedCount = removed;
numeralCountBeforeSelection = countNumerals(priorFormattedString.substring(0, changeStart));
numeralCountRemoved = countNumerals(priorFormattedString.substring(changeStart, changeStart + removedCount));
}
/**
* Call in onTextChanged to record new text and how many characters were added after changeStart
*
* #param afterEdit new string after user input
* #param added how many characters were added (same start position as before)
*/
public void recordChangedText(String afterEdit, int added) {
addition = (added > 0);
additionCount = added;
numeralCountAdded = countNumerals(afterEdit.substring(changeStart, changeStart + additionCount));
currentNumericalString = afterEdit.replaceAll("[^0-9]", "");
}
/**
* Re-process the edit for our particular formatting needs.
*/
public void processEdit() {
forceRemovalPastFormatting();
finalizeEdit();
}
/**
* #return Integer value of the field after an edit.
*/
public int getCurrentRawValue() {
return currentRawValue;
}
/**
* #return Formatted number after an edit.
*/
public String getCurrentFormattedString() {
return currentFormattedString;
}
/**
* #return Cursor position after an edit
*/
public int getCursorPosition() {
int numeralPosition = numeralCountBeforeSelection + numeralCountAdded;
return positionAfterNumeralN(currentFormattedString,numeralPosition);
}
/**
* If a user deletes a value, but no numerals are deleted, then delete the numeral proceeding
* their cursor. Otherwise, we'll just add back the formatting character.
*
* Assumes formatting uses a single character and not multiple formatting characters in a row.
*/
private void forceRemovalPastFormatting() {
if (removal && (!addition) && (numeralCountRemoved == 0)) {
String before = currentNumericalString.substring(0, numeralCountBeforeSelection - 1);
String after = currentNumericalString.substring(numeralCountBeforeSelection);
currentNumericalString = before + after;
numeralCountRemoved++;
numeralCountBeforeSelection--;
}
}
/**
* Determine the result of the edit, including new display value and raw value
*/
private void finalizeEdit() {
currentFormattedString = "";
currentRawValue = 0;
if (currentNumericalString.length() == 0) {
return; // There is no entry now.
}
try {
currentRawValue = Integer.parseInt(currentNumericalString);
} catch (NumberFormatException nfe) {
abortEdit(); // Value is not an integer, return to previous state.
return;
}
currentFormattedString = String.format("%,d", currentRawValue);
}
/**
* Current text, same as the old text.
*/
private void abortEdit() {
currentFormattedString = priorFormattedString;
currentNumericalString = currentFormattedString.replaceAll("[^0-9]", "");
numeralCountRemoved = 0;
numeralCountAdded = 0;
try {
currentRawValue = Integer.parseInt(currentNumericalString);
} catch (Exception e) { currentRawValue = 0; }
}
/**
* Determine how many numerical characters exist in a string
* #param s
* #return the number of numerical characters in the string
*/
private int countNumerals(String s) {
String newString = s.replaceAll("[^0-9]", "");
return newString.length();
}
/**
* Determine how to place a cursor after the Nth Numeral in a formatted string.
* #param s - Formatted string
* #param n - The position of the cursor should follow the "Nth" number in the string
* #return the position of the nth character in a formatted string
*/
private int positionAfterNumeralN(String s, int n) {
int numeralsFound = 0;
if (n == 0) {
return 0;
}
for (int i = 0; i < s.length(); i++) {
if(s.substring(i,i+1).matches("[0-9]")) {
if(++numeralsFound == n) {
return i + 1;
}
}
}
return s.length();
}
}
}
At a highlevel, what that does is:
Determine which numbers were actually in the string after it was edited
Process the edit to the numeral version of the string if the numbers weren't edited
Convert the numeral back to a formatted string
Determine, where the cursor should be based on where editing began and how much text was added
It also nicely handles edge cases like completely deleted input, integer overflow and erroneous input.
You can use a custom TextInputEditText :
public class NumberTextInputEditText extends TextInputEditText {
public NumberTextInputEditText(#NonNull Context context) {
super(context);
}
public NumberTextInputEditText(#NonNull Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public NumberTextInputEditText(#NonNull Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
addTextChangedListener(textWatcher);
}
public String formatNumber(double number) {
DecimalFormat decimalFormat = new DecimalFormat("#,###");
return decimalFormat.format(number);
}
public TextWatcher textWatcher = 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) {
removeTextChangedListener(this);
String text = getText().toString();
String format = "";
if (!TextUtils.isEmpty(text)) {
try {
format = formatNumber(Double.parseDouble(new BigDecimal(text.replaceAll(",", "")).toString()));
} catch (NumberFormatException e) {
format = "";
}
setText(format);
setSelection(format.length());
}
addTextChangedListener(this);
}
};}
just use it like a view in your layout:
<com.your.package.name.NumberTextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Here i have tested my application code. text-watcher how to add comma in currency thousand, lake currency.
private TextWatcher textWatcherAmount = 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) {
String initial = s.toString();
if (inputEdtHawalaRate == null) return;
if (!TextUtils.isEmpty(initial)) {
initial = initial.replace(",", "");
NumberFormat formatter = new DecimalFormat("##,##,###");
inputEdtHawalaRate.removeTextChangedListener(this);
double myNumber = Double.parseDouble(initial);
String processed = formatter.format(myNumber);
//Assign processed text
inputEdtHawalaRate.setText(processed);
try {
inputEdtHawalaRate.setSelection(processed.length());
} catch (Exception e) {
e.printStackTrace();
}
//Give back the listener
inputEdtHawalaRate.addTextChangedListener(this);
}
}
#Override
public void afterTextChanged(Editable s) {
}
};
if (inputEdtHawalaRate != null) {
inputEdtHawalaRate.addTextChangedListener(textWatcherAmount);
}
// getting amount on double type varaible (On textwatcher editetxt value get).
String amount = Objects.requireNonNull(inputEdtHawalaRate.getText()).toString().trim();
double hawalaAmount = 0.0;
String[] a = amount.split(",");
finalAmount = TextUtils.join("", a);
hawalaAmount = Double.parseDouble(finalAmount);
I was looking for a locale aware solution since we have customers across the globe. So I built upon dr0pdb's answer.
Here's a TextWatcher class (in kotlin) I have created to solve this.
https://github.com/abhilashd-locus/edittext-locale-aware-thousands-separator
Features:
Add thousands separator dynamically as the user types
Enable editing in between the string and not only at the ends
Style of thousands separation is based upon the locale (eg: 100,000 vs 1,00,000)
Symbol of thousands separator and decimal marker is based on the locale (eg: 100,000.00 vs 100.000,00)
Supports all languages and locales
Disadvantages:
Does not support copy/paste operations
In right-to-left languages (eg. Arabic), the cursor jumps to the end on deleting the first number
.
// ThousandsSeparatorTextWatcher.kt --> add this TextWatcher to the
// EditText you want to add the functionality of dynamic locale aware thousands separator
class ThousandsSeparatorTextWatcher(private var editText: EditText?, private val callback: TextChangedCallback) : TextWatcher {
//keeping a count of the digits before the cursor to reset the cursor at the correct place
private var digitsBeforeCursor = -1
private val thousandSeparator: Char = DecimalFormatSymbols(Locale.getDefault()).groupingSeparator
private val decimalMarker: Char = DecimalFormatSymbols(Locale.getDefault()).decimalSeparator
init {
editText?.apply {
addTextChangedListener(this#ThousandsSeparatorTextWatcher)
//disabling copy/paste to avoid format and parse errors
disableTextSelection(this)
//diabling text selection
isLongClickable = false
setTextIsSelectable(false)
//ensuring correct input type
keyListener = DigitsKeyListener.getInstance("0123456789$decimalMarker");
}
}
private fun disableTextSelection(editText: EditText) {
editText.customSelectionActionModeCallback = object : android.view.ActionMode.Callback {
override fun onActionItemClicked(mode: android.view.ActionMode?, item: MenuItem?) = false
override fun onCreateActionMode(mode: android.view.ActionMode?, menu: Menu?) = false
override fun onPrepareActionMode(mode: android.view.ActionMode?, menu: Menu?) = false
override fun onDestroyActionMode(mode: android.view.ActionMode?) {}
}
}
/***
* We are going to calculate the number of numeric digits before the cursor when user starts editing
* We will keep a count of this number to reset the cursor to the correct position after editing is complete
*/
override fun beforeTextChanged(sequenceBeforeEdit: CharSequence, startPos: Int, count: Int, after: Int) {
val textBeforeEdit = sequenceBeforeEdit.toString()
if (textBeforeEdit.isEmpty()) {
//in an empty string, cursor position is at 1 if a character is being added (after == 1)
//if a character is not being added, cursor position remains at the beginning
digitsBeforeCursor = if (after == 0) -1 else 1
return
}
digitsBeforeCursor = if (after == 0) {
//if characters are being removed
//count will always be 1 since we have disabled selection (in which case count will be equal to the number of characters selected)
val textBeforeNewCursor = textBeforeEdit.substring(0, startPos)
textBeforeNewCursor.count { it != thousandSeparator }
} else {
//if characters are being added
//after will always be 1 since we have disabled pasting (in which case after will be equal to the number of characters being pasted)
if (startPos == textBeforeEdit.length) {
//if adding a character to the end of the string
textBeforeEdit.count { it != thousandSeparator } + 1
} else {
//if adding a character in between the string
val textBeforeNewCursor = textBeforeEdit.substring(0, startPos + 1)
textBeforeNewCursor.count { it != thousandSeparator }
}
}
}
override fun onTextChanged(textAfterEdit: CharSequence, start: Int, before: Int, count: Int) {}
/***
* We will get the numeric value in the editText after stripping all the formatting
* We will then reformat this number to add the correct thousands separation and decimal marker according to the locale
* We then set the cursor to the correct position as we calculated in beforeTextChanged()
*/
override fun afterTextChanged(editable: Editable) {
val text = editable.toString()
//if the EditText is cleared, trigger callback with a null value to indicate an empty field
if (text.isEmpty()) {
digitsBeforeCursor = -1
callback.onChanged(null)
return
}
//get the double value of the entered number
val numberValue = getNumberFromFormattedCurrencyText(text)
//re-format the number to get the correct separation format and symbols
var newText = getCurrencyFormattedAmountValue(numberValue)
//If user was inputting decimal part of the number, reformatting will return a string without decimal point.
//So we need to add it back after the reformatting is complete
if (text.endsWith(decimalMarker)) {
newText += decimalMarker
} else if (text.endsWith(decimalMarker + "0")) {
newText += decimalMarker + "0"
}
//removing the listener to prevent infinite triggers
editText?.removeTextChangedListener(this)
//set the reformatted text
editText?.setText(newText)
//send the number typed to the callback
callback.onChanged(numberValue)
//set the cursor to the right position after reformatting the string
if (digitsBeforeCursor != -1) {
var numbersParsed = 0
for (i in newText.indices) {
if (newText[i] != thousandSeparator) {
numbersParsed++
}
if (numbersParsed == digitsBeforeCursor) {
editText?.setSelection(i + 1)
break
}
}
digitsBeforeCursor = -1
}
//add the listener back
editText?.addTextChangedListener(this)
}
/***
* Function to remove the listener and release reference to the EditText
*/
fun removeWatcherFromEditText() {
editText?.removeTextChangedListener(this)
editText = null
}
interface TextChangedCallback {
fun onChanged(newNumber: Double?)
}
companion object{
#JvmStatic
fun getNumberFromFormattedCurrencyText(formattedText: String?) = formattedText?.let {
val numberFormat = NumberFormat.getNumberInstance(Locale.getDefault())
try {
numberFormat.parse(it)?.toDouble()
} catch (exception: ParseException) {
0.0
}
} ?: 0.0
#JvmStatic
fun getCurrencyFormattedAmountValue(amount: Double?) = amount?.let {
val numberFormat = NumberFormat.getNumberInstance(Locale.getDefault())
numberFormat.maximumFractionDigits = 2
numberFormat.format(amount)
} ?: ""
}
}
I know it's late but maybe can help
fun generate_seprators(input: String?): String? {
var input = input
var result = ""
var float_section = ""
if (input == null) input = ""
var temp = input.trim { it <= ' ' }
temp = temp.replace(",", "")
var input_array = temp.split(".")
var decimal_section = input_array[0]
if(input_array.size>1)
float_section = input_array[1]
if (decimal_section.length > 3) {
var num = 0
for (i in decimal_section.length downTo 1) {
if (num == 3) {
num = 0
result = ",$result"
}
num++
result = decimal_section.substring(i - 1, i) + result
}
if(float_section!="")
result = "$result.$float_section"
} else {
result = decimal_section.replace(",", "")
if(float_section!="")
result = "$result.$float_section"
}
return result
}

Categories

Resources