Andorid TextView, Linkify US phone number in every language - android

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").

Related

How can I convert numbers to currency format in android

I want to show my numbers in money format and separate digits like the example below:
1000 -----> 1,000
10000 -----> 10,000
100000 -----> 100,000
1000000 -----> 1,000,000
Thanks
Another approach :
NumberFormat format = NumberFormat.getCurrencyInstance();
format.setMaximumFractionDigits(0);
format.setCurrency(Currency.getInstance("EUR"));
format.format(1000000);
This way, it's displaying 1 000 000 € or 1,000,000 €, depending on device currency's display settings
You need to use a number formatter, like so:
NumberFormat formatter = new DecimalFormat("#,###");
double myNumber = 1000000;
String formattedNumber = formatter.format(myNumber);
//formattedNumber is equal to 1,000,000
Hope this helps!
double number = 1000000000.0;
String COUNTRY = "US";
String LANGUAGE = "en";
String str = NumberFormat.getCurrencyInstance(new Locale(LANGUAGE, COUNTRY)).format(number);
//str = $1,000,000,000.00
Currency formatter.
public static String currencyFormat(String amount) {
DecimalFormat formatter = new DecimalFormat("###,###,##0.00");
return formatter.format(Double.parseDouble(amount));
}
Use this:
int number = 1000000000;
String str = NumberFormat.getNumberInstance(Locale.US).format(number);
//str = 1,000,000,000
This Method gives you the exact output which you need:
public String currencyFormatter(String num) {
double m = Double.parseDouble(num);
DecimalFormat formatter = new DecimalFormat("###,###,###");
return formatter.format(m);
}
Try the following solution:
NumberFormat format = NumberFormat.getCurrencyInstance();
((TextView)findViewById(R.id.text_result)).setText(format.format(result));
The class will return a formatter for the device default currency.
You can refer to this link for more information:
https://developer.android.com/reference/java/text/NumberFormat.html
Here's a kotlin Extension that converts a Double to a Currency(Nigerian Naira)
fun Double.toRidePrice():String{
val format: NumberFormat = NumberFormat.getCurrencyInstance()
format.maximumFractionDigits = 0
format.currency = Currency.getInstance("NGN")
return format.format(this.roundToInt())
}
Use a Formatter class
For eg:
String s = (String.format("%,d", 1000000)).replace(',', ' ');
Look into:
http://developer.android.com/reference/java/util/Formatter.html
The way that I do this in our app is this:
amount.addTextChangedListener(new CurrencyTextWatcher(amount));
And the CurrencyTextWatcher is this:
public class CurrencyTextWatcher implements TextWatcher {
private EditText ed;
private String lastText;
private boolean bDel = false;
private boolean bInsert = false;
private int pos;
public CurrencyTextWatcher(EditText ed) {
this.ed = ed;
}
public static String getStringWithSeparator(long value) {
DecimalFormat formatter = (DecimalFormat) NumberFormat.getNumberInstance(Locale.US);
String f = formatter.format(value);
return f;
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
bDel = false;
bInsert = false;
if (before == 1 && count == 0) {
bDel = true;
pos = start;
} else if (before == 0 && count == 1) {
bInsert = true;
pos = start;
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
lastText = s.toString();
}
#Override
public void afterTextChanged(Editable s) {
ed.removeTextChangedListener(this);
StringBuilder sb = new StringBuilder();
String text = s.toString();
for (int i = 0; i < text.length(); i++) {
if ((text.charAt(i) >= 0x30 && text.charAt(i) <= 0x39) || text.charAt(i) == '.' || text.charAt(i) == ',')
sb.append(text.charAt(i));
}
if (!sb.toString().equals(s.toString())) {
bDel = bInsert = false;
}
String newText = getFormattedString(sb.toString());
s.clear();
s.append(newText);
ed.addTextChangedListener(this);
if (bDel) {
int idx = pos;
if (lastText.length() - 1 > newText.length())
idx--; // if one , is removed
if (idx < 0)
idx = 0;
ed.setSelection(idx);
} else if (bInsert) {
int idx = pos + 1;
if (lastText.length() + 1 < newText.length())
idx++; // if one , is added
if (idx > newText.length())
idx = newText.length();
ed.setSelection(idx);
}
}
private String getFormattedString(String text) {
String res = "";
try {
String temp = text.replace(",", "");
long part1;
String part2 = "";
int dotIndex = temp.indexOf(".");
if (dotIndex >= 0) {
part1 = Long.parseLong(temp.substring(0, dotIndex));
if (dotIndex + 1 <= temp.length()) {
part2 = temp.substring(dotIndex + 1).trim().replace(".", "").replace(",", "");
}
} else
part1 = Long.parseLong(temp);
res = getStringWithSeparator(part1);
if (part2.length() > 0)
res += "." + part2;
else if (dotIndex >= 0)
res += ".";
} catch (Exception ex) {
ex.printStackTrace();
}
return res;
}
Now if you add this watcher to your EditText, as soon as user enter his number, the watcher decides whether it needs separator or not.
i used this code for my project and it works:
EditText edt_account_amount = findViewById(R.id.edt_account_amount);
edt_account_amount.addTextChangedListener(new DigitFormatWatcher(edt_account_amount));
and defined class:
public class NDigitCardFormatWatcher implements TextWatcher {
EditText et_filed;
String processed = "";
public NDigitCardFormatWatcher(EditText et_filed) {
this.et_filed = et_filed;
}
#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 editable) {
String initial = editable.toString();
if (et_filed == null) return;
if (initial.isEmpty()) return;
String cleanString = initial.replace(",", "");
NumberFormat formatter = new DecimalFormat("#,###");
double myNumber = new Double(cleanString);
processed = formatter.format(myNumber);
//Remove the listener
et_filed.removeTextChangedListener(this);
//Assign processed text
et_filed.setText(processed);
try {
et_filed.setSelection(processed.length());
} catch (Exception e) {
// TODO: handle exception
}
//Give back the listener
et_filed.addTextChangedListener(this);
}
}
Updated 2022 answer
Try this snippet. It formats a number in string complete with the currency & setting fractional digits.
Upvote if this helped you! :)
/**
* Formats amount in string to human-readable amount (separated with commas
* & prepends currency symbol)
*
* #param amount The amount to format in String
* #return The formatted amount complete with separators & currency symbol added
*/
public static String formatCurrency(String amount) {
String formattedAmount = amount;
try {
if (amount == null || amount.isEmpty())
throw new Exception("Amount is null/empty");
Double amountInDouble = Double.parseDouble(amount);
NumberFormat numberFormat = NumberFormat.getCurrencyInstance(new Locale("en", "IN"));
numberFormat.setMaximumFractionDigits(2);
numberFormat.setMinimumFractionDigits(2);
formattedAmount = numberFormat.format(amountInDouble);
} catch (Exception exception) {
exception.printStackTrace();
return formattedAmount;
}
return formattedAmount;
}
private val currencyFormatter = NumberFormat.getCurrencyInstance(LOCALE_AUS).configure()
private fun NumberFormat.configure() = apply {
maximumFractionDigits = 2
minimumFractionDigits = 2
}
fun Number.asCurrency(): String {
return currencyFormatter.format(this)
}
And then just use as
val x = 100000.234
x.asCurrency()
If you have the value stored in a String like me, which was coming from the server like "$20000.00".
You can do something like this in Kotlin (JetpackCompose):
#Composable
fun PrizeAmount(
modifier: Modifier = Modifier,
prize: String,
)
{
val currencyFormat = NumberFormat.getCurrencyInstance(Locale("en", "US"))
val text = currencyFormat.format(prize.substringAfter("$").toDouble())
...
}
Output: "$20,000.00"
NumberFormat.getCurrencyInstance(Locale("ES", "es")).format(number)
here is a kotlin version to Format Currency, here i'm getting an argument from another fragment from an input Field then it will be set in the textView in the main Fragment
fun formatArgumentCurrency(argument : String, textView: TextView) {
val valueText = requireArguments().get(argument).toString()
val dec = DecimalFormat("#,###.##")
val number = java.lang.Double.valueOf(valueText)
val value = dec.format(number)
val currency = Currency.getInstance("USD")
val symbol = currency.symbol
textView.text = String.format("$symbol$value","%.2f" )
}
You can easily achieve this with this small simple library.
https://github.com/jpvs0101/Currencyfy
Just pass any number, then it will return formatted string, just like that.
currencyfy (500000.78); // $ 500,000.78 //default
currencyfy (500000.78, false); // $ 500,001 // hide fraction (will round off automatically!)
currencyfy (500000.78, false, false); // 500,001 // hide fraction & currency symbol
currencyfy (new Locale("en", "in"), 500000.78); // ₹ 5,00,000.78 // custom locale
It compatible with all versions of Android including older versions!

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());

android numberpicker can't read keyboard number input

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());
}
}

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
}

How to set custom (arial )type face via xml not via a java code

I already aware of this:-
Typeface typefaceArial= Typeface.createFromAsset(context.getAssets(), "arial.ttf");
but when I create the following class it works but it gives low memory warning issue.
public class MidColorTextView extends TextView
{
private CharSequence text;
private String token;
private static Context context;
private String colorSpan;
private int colorCode;
private static Typeface typefaceArial;
public MidColorTextView( Context context , AttributeSet attrs )
{
super(context, attrs);
this.context=null;
this.context = context;
for(int i = 0; i < attrs.getAttributeCount(); i ++ )
{
// Log.i(TAG, attrs.getAttributeName(i));
/*
* Read value of custom attributes
*/
this.text = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.lht", "text");
this.token = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.lht", "token");
this.colorSpan = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.lht", "colorSpan");
// Log.i("TAG", "token " + token);
// Log.i("TAG", "text " + text);
// Log.i("TAG", "colorSpan " + colorSpan);
}
init();
}
private void init ()
{
if(text.charAt(0) == '#')
{
String tempText = (String) text.subSequence(1, text.length());
this.text = Html.fromHtml(getResources().getString(Integer.parseInt(tempText)));
}
if(token.charAt(0) == '#')
{
String tempText = (String) token.subSequence(1, token.length());
this.token = getResources().getString(Integer.parseInt(tempText));
}
if(colorSpan.charAt(0) == '#')
{
String tempText = (String) colorSpan.subSequence(1, colorSpan.length());
this.colorSpan = getResources().getString(Integer.parseInt(tempText));
}
setColorCode(Color.parseColor(colorSpan));
CharSequence textWitoutToken = null;
String tempString = text.toString();
// ---------checking whether text containg token or not.
if(tempString.contains(token))
{
textWitoutToken = setSpanBetweenTokens(text, token, new ForegroundColorSpan(colorCode));
}
else
{
textWitoutToken = text;
}
textContent = null;
setText(textWitoutToken);
setTypefaceArial ();
setTypeface(getTypefaceArial ());
}
public int getColorCode ()
{
return colorCode;
}
public void setColorCode ( int colorCode )
{
this.colorCode = colorCode;
}
private CharSequence textContent;
public static Typeface getTypefaceArial ()
{
return typefaceArial;
}
public static void setTypefaceArial ()
{
MidColorTextView.typefaceArial= Typeface.createFromAsset(context.getAssets(), "arial.ttf");
}
}
I solved this issue via using singleton class.
I am giving the complete code so that it can help others.
1. Define this in XML:
xmlns:lht="http://schemas.android.com/apk/res/com.lht" android:id="#+id/basicLayout"
<com.xyz.util.MidColorTextView
xyz:token="#"
xyz:colorSpan="#color/BrightRed"
xyz:text="#string/AppraisingStaffBottomText_imanage"
style="#style/contentDescriptionText" />
2. Create class MidColorTextView
package com.xyz.util;
public class MidColorTextView extends TextView {
private CharSequence text;
private String token;
private Context context;
private String colorSpan;
private int colorCode;
public MidColorTextView( Context context , AttributeSet attrs ) {
super(context, attrs);
this.context = context;
for(int i = 0; i < attrs.getAttributeCount(); i ++ ) {
// Log.i(TAG, attrs.getAttributeName(i));
/*
* Read value of custom attributes
*/
this.text = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.xyz", "text");
this.token = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.xyz", "token");
this.colorSpan = attrs.getAttributeValue("http://schemas.android.com/apk/res/com.xyz", "colorSpan");
// Log.i("TAG", "token " + token);
// Log.i("TAG", "text " + text);
// Log.i("TAG", "colorSpan " + colorSpan);
}
init();
}
private void init () {
if(text.charAt(0) == '#') {
String tempText = (String) text.subSequence(1, text.length());
this.text = Html.fromHtml(getResources().getString(Integer.parseInt(tempText)));
}
if(token.charAt(0) == '#') {
String tempText = (String) token.subSequence(1, token.length());
this.token = getResources().getString(Integer.parseInt(tempText));
}
if(colorSpan.charAt(0) == '#')
{
String tempText = (String) colorSpan.subSequence(1, colorSpan.length());
this.colorSpan = getResources().getString(Integer.parseInt(tempText));
}
setColorCode(Color.parseColor(colorSpan));
CharSequence textWitoutToken = null;
String tempString = text.toString();
// ---------checking whether text containg token or not.
if(tempString.contains(token))
{
textWitoutToken = setSpanBetweenTokens(text, token, new ForegroundColorSpan(colorCode));
}
else
{
textWitoutToken = text;
}
textContent = null;
setText(textWitoutToken);
setTypeface(FontManager.getInstance(context).getTypefaceArial ());
}
public void setText ( CharSequence text , String token , int color )
{
setText(setSpanBetweenTokens(text, token, new ForegroundColorSpan(color)));
setTypeface(FontManager.getInstance(context).getTypefaceArial ());
}
public int getColorCode ()
{
return colorCode;
}
public void setColorCode ( int colorCode )
{
this.colorCode = colorCode;
}
private CharSequence textContent;
public CharSequence setSpanBetweenTokens ( CharSequence text , String token , CharacterStyle... cs )
{
// Start and end refer to the points where the span will apply
int tokenLen = token.length();
int start = text.toString().indexOf(token) + tokenLen;
int end = text.toString().indexOf(token, start);
if(start > - 1 && end > - 1)
{
// Copy the spannable string to a mutable spannable string
SpannableStringBuilder ssb = new SpannableStringBuilder(text);
for(CharacterStyle c : cs)
{
ssb.setSpan(c, start, end, 0);
}
// Delete the tokens before and after the span
ssb.delete(end, end + tokenLen);
ssb.delete(start - tokenLen, start);
text = ssb;
textContent = ssb;
String tempString = textContent.toString();
if(tempString.contains(token))
{
setSpanBetweenTokens(textContent, token, new ForegroundColorSpan(colorCode));
}
}
return textContent;
}
}
3. Create class FontManager
public class FontManager {
private Typeface typefaceArial;
private Context context;
private static FontManager instance = null;
private FontManager(Context context) {
this.context = context;
this.typefaceArial= Typeface.createFromAsset(context.getAssets(), "arial.ttf");
}
public synchronized static FontManager getInstance(Context context) {
if(instance == null) {
instance = new FontManager(context);
}
return instance;
}
public Typeface getTypefaceArial () {
return typefaceArial;
}
}
This will solve all your problems.
setSpanBetweenTokens is used for the color text between specific tokens.
Here is a string resource to test on:
<string name="AppraisingStaffBottomText_imanage">The meeting<br><br>PAST<br>Allow the employee to give you
their view of their positive
progress over the past period, focus them on this with open
questions, such as:<br><i>#\"What has been your important contribution over the past
6
months?\"#</i><br><i>#\"What have your learned about your role?\"#</i><br>
<i>#\"What has been your important success?\"#</i><br>Don\'t rake over past mistakes,
don\'t focus on poor performance
- you cannot change that, reserve those discussions future
development - see below<br><br>PRESENT<br>Using open questions, help staff to identify
their true strengths,
capabilities, attributes, skills and attitudes. Create a
comprehensive picture of them as a strategic contributor and
resource.<br>What are your skills, and to what level?<br><i>#\"What have you added as
capabilities over the past months?\"#</i><br><i>#\"What do you find are your
most useful personal attributes in
your role?\"#</i><br><br>FUTURE<br>The future is the period where changes in
capability and
performance can be made.<br>This discussion is where your people can figure out -
with your
help - what development they need to reach your performance
standards and their career goals. It begins with understanding
their career goals, so ....<br><i>#\"What are your goals?\"#</i><br><i>#\"What
development will be needed?\"#</i>
<br><i>#\"You have seen over the past months that you may need more skill in these
areas .......................... what should we do about
that?\"#</i><br><br>Finally: Agree a specific development plan that includes
training/experience in the areas where more skill is needed. Fix dates
in the diary<br><br>The Manager\'s role is one of Mentor and Guide; not
Judge and
Jury</string>
public class MidColorTextView extends TextView {
private String token;
private static Context context;
private String colorSpan;
private int colorCode;
private static Typeface typefaceArial;
public MidColorTextView( Context context , AttributeSet attrs )
{
super(context, attrs);
this.context = context;
init();
}
private void init ()
{
setTypefaceArial ();
setTypeface(getTypefaceArial ());
}
public int getColorCode ()
{
return colorCode;
}
public void setColorCode ( int colorCode )
{
this.colorCode = colorCode;
}
private CharSequence textContent;
public static Typeface getTypefaceArial ()
{
return typefaceArial;
}
public static void setTypefaceArial ()
{
MidColorTextView.typefaceArial= Typeface.createFromAsset(context.getAssets(), "arial.ttf");
}
}
You can apply the typeface as a style
But a specific font, only in java code: ApiDemo
To understand what is causing the low memory error,
make sure it is not a problem with that particular font (for example, try this font, which has been used in this example of a class extending TextView.
reduce the non-specific code and test your class with a bare minimum code (i.e., typefaceArial=Typeface.createFromAsset(context.getAssets(), "arial.ttf") and this.text ="Testing text"; as part of the constructor, eliminating all the attrs.getAttributeValue() methods and specially the init() method, etc.)
Once you get those two changes, you will be able to tell whether is the Typeface assignment a problem, or something else.

Categories

Resources