How to dynamically change the text size in Android - java

I want to implement a function,
EditText user when entering text, you can make changes in accordance with the set font size,
Such as Google Docs of Office,
Now I found a way to SpannableString, but read some examples seem unable to reach my needs
int index = input.getSelectionEnd();
SpannableString spann = new SpannableString(show_input);
AbsoluteSizeSpan word_size = new AbsoluteSizeSpan(18,true);
spann.setSpan(word_size, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
input.getText().insert(index, spann);
So is there any way to provide it?

Looks like you want to change size of selected text:
int start = editText.getSelectionStart();
int end = editText.getSelectionEnd();
Spannable text=(Spannable)editText.getText();
text.setSpan(new AbsoluteSizeSpan(NEW_TEXT_SIZE, true), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
editText.setText(text);
Use AbsoluteSizeSpan to change for actual size or RelativeSizeSpan for proportional size.

I trird to add TextWatcher(),
input =(EditText)findViewById(R.id.Input_EditText);
input.addTextChangedListener(new TextWatcher()
{
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void afterTextChanged(Editable s)
{
int index = input.getSelectionEnd();
String nowstr;
if(index==0)
{
//do nothing
}
else
{
nowstr = s.toString();
char nowstr_array[] = nowstr.toCharArray();
show_input = String.valueOf(nowstr_array[index-1]);
SpannableStringBuilder spann = new SpannableStringBuilder(show_input);
AbsoluteSizeSpan word_size = new AbsoluteSizeSpan(40,true);
spann.setSpan(word_size, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
input.getText().insert(index, spann);
}
}
});
But this program will crash...
then,I try to do
Toast.makeText(MainActivity.this, spann, Toast.LENGTH_SHORT).show();
//input.getText().insert(index, spann);
But doing so can be displayed...
That is why?

Related

Min & Max Input values on Android Studio [duplicate]

This question already has answers here:
Is there a way to define a min and max value for EditText in Android?
(29 answers)
Closed 11 months ago.
I am creating an app that calculates the the users marks. How do I go forward, I'm trying to limit the user's input value to between min=0 and max=100?
I've got 3 inputs that I want to limit.
EditText a = (EditText) findViewById(R.id.Assignment1);
EditText b = (EditText) findViewById(R.id.Assignment2);
EditText c = (EditText) findViewById(R.id.Assignment3);
Tried using the following but only works when I execute my function.
public void YearMark(View v)
{
EditText a = (EditText) findViewById(R.id.Assignment1);
a.setFilters(new InputFilter[]{ new InputFilterMinMax("0", "100")});
As #JAY_Panchal mentioned, you can use an InputFilter:
public class RangeInputFilter implements InputFilter {
private final int min, max;
public RangeInputFilter(int min, int max) {
boolean rightOrder = min <= max;
this.min = rightOrder ? min : max;
this.max = rightOrder ? max : min;
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dStart, int dEnd) {
try {
String sourceStr = source.toString();
String destStr = dest.toString();
String result = destStr.substring(0, dStart) + sourceStr.substring(start, end) + destStr.substring(dEnd);
int input = Integer.parseInt(result);
if (min <= input && input <= max) return null;
} catch (NumberFormatException ignored) { }
return "";
}
}
When a user changes the content of an EditText, you can use an InputFilter to decide what actually changes in the EditText.
Returning null means that the input is accepted.
Returning something else (in this case "") means that source should be replaced with that (in this case, we want dest to stay as it is and thus have an empty source).
To assign your filter:
RangeInputFilter filter = new RangeInputFilter(0, 100);
InputFilter[] filters = new InputFilter[] {filter};
editText1.setFilters(filters);
editText2.setFilters(filters);
editText3.setFilters(filters);
Add this textwatcher for all three edittext a,b and c:
a.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {
}
#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 strTest = editText1235.getText().toString().trim();
if (!strTest.equals("")) {
float no = Float.parseFloat(strTest);
if (no > 100) {
a.setText(null);
Toast.makeText(this, "enter a value less than 100", Toast.LENGTH_SHORT).show();
}
}
}
});
Also set input type of these text fields as number so that user cannot enter a negative sign,ie, any value less than 0.
android:inputType="number"
While submitting the details also check if the field is not empty like below:
if (a.getText().toString().trim().length() <=0 ||b.getText().toString().trim().length() <=0 ||c.getText().toString().trim().length() <= 0){
Toast.makeText(this, "Value cannot be empty", Toast.LENGTH_SHORT).show();
}
I don't know your usecase, but you might also want to use a SeekBar.
A SeekBar is a slider - you can select a number by sliding. It is also very customizable.
The main advantage is that the user does not need to open the keyboard.

Add quotation mark programatically to EditText

I created an EditText where users can type a sentence in it. I would like to automatically add quotation marks to it at the start and end of the sentence.
For example, If a user type: Believe you can and you're halfway there., I would like it to show in the EditText as:
If there is an option to add an image of a quotation mark and to make sure that it moves with the length of the string it could also do the job.
I tried something like this but my app crashes:
et_Quote.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
et_Quote.setText( "\" " + s.toString() + "\" ");
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
Thank you
Inside onTextChanged you are calling setText, which calls onTextChanged inside which you are calling setText and so on... locked in loop, UI thread hangs
Consider adding some flag preventing multiple onTextChanged calls in a row
et_Quote.addTextChangedListener(new TextWatcher() {
private boolean hold = false;
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(hold) return;
hold = true;
et_Quote.setText( "\" " + s.toString() + "\"");
hold = false;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
Note that with every character entered into et_Quote you are adding " at the beginning and end of String, so there will be multiple " characters, two per every character entered. add some code for checking if first/last character is already a ", if yes then don't add it obviously
boolean isFirst = s.length() != 0 && s.charAt(0) == '"';
boolean isLast = s.length() != 0 && s.charAt(s.length() - 1) == '"';
String toSet = (isFirst ? "" : "\" ") + s.toString().trim() + (isLast ? "" : "\"");
trim() method removes unnecessary whitespaces at the beginning and end of String (you can't call it on CharSequence, thus toString() used in there
If you are using hard text you can try this out.
android:text='"Hello"'
If not then try this out.
InputFilter filter = new InputFilter() {
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
char[] chars = {'\'','"'};
for (int i = start; i < end; i++) {
if (new String(chars).contains(String.valueOf(source.charAt(index))) {
return "";
}
}
return null;
}
};
edit.setFilters(new InputFilter[] { filter });

Remove span when text inside the span is changed in Android

Let´s say I make a comment like this:
Hi Andrea check this out..... In that comment I want to highlight Andrea, but whenever I change the value of Andrea or when I delete one character of the word the span changes, the problem is that I´m using spannableString.setSpan(new StyleSpan(Typeface.BOLD), indexOfAt, highlightName.length() + indexOfAt, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE), which Spanned.SPAN_EXCLUSIVE_EXCLUSIVE accepts words in the middle and deletion of words, how can I remove the span when the user changes the word or delete a character of the word?
You need to watch for Text change on the edit text.
Assuming the EditText, you are using is named as commentEditText
commentEditText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
String comment = editable.toString();
if (comment.indexOf('#') != -1) {
//Asumming the name string
String name = "Andrea";
int atIndex = comment.indexOf('#');
int endIndex = atIndex + name.length() + 1;
if (endIndex == -1 || endIndex > editable.length()) {
endIndex = editable.length();
}
if (comment.toLowerCase().contains(name.toLowerCase())) {
editable.setSpan(new StyleSpan(Typeface.BOLD), atIndex, endIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
StyleSpan[] spannable = editable.getSpans(atIndex, endIndex, StyleSpan.class);
if (spannable != null && spannable.length > 0) {
for (int i = 0; i < spannable.length; i++) {
editable.removeSpan(spannable[i]);
}
}
}
}
}
});
If you are using the SpannableString you have to recreate the whole thing on every change.
You can remove spans but you cannot change the source (the comment) because SpannableString source text is immutable.
I suggest on every change of the comment you create the SpannableString, look for names and tag them, then if the comment changes you repeat the same thing with a new SpannableString. There will be no performance problems because the comments are small in size.
If you want to have a mutable object you can use the SpannableStringBuilder but it's more complicated and there is no need for it.
You can try TextWatcher for this, simply add TextWatcher to the textview you want to achieve above and in on
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//Do some magic here
}
Now whenever user types or deletes any character you will get a callback in this method upon receiving that you can reset your span.

Limit after dot in EditText [duplicate]

I'm trying to write an app that helps you manage your finances. I'm using an EditText Field where the user can specify an amount of money.
I set the inputType to numberDecimal which works fine, except that this allows people to enter numbers such as 123.122 which is not perfect for money.
Is there a way to limit the number of characters after the decimal point to two?
More elegant way would be using a regular expression ( regex ) as follows:
public class DecimalDigitsInputFilter implements InputFilter {
Pattern mPattern;
public DecimalDigitsInputFilter(int digitsBeforeZero,int digitsAfterZero) {
mPattern=Pattern.compile("[0-9]{0," + (digitsBeforeZero-1) + "}+((\\.[0-9]{0," + (digitsAfterZero-1) + "})?)||(\\.)?");
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
Matcher matcher=mPattern.matcher(dest);
if(!matcher.matches())
return "";
return null;
}
}
To use it do:
editText.setFilters(new InputFilter[] {new DecimalDigitsInputFilter(5,2)});
Simpler solution without using regex:
import android.text.InputFilter;
import android.text.Spanned;
/**
* Input filter that limits the number of decimal digits that are allowed to be
* entered.
*/
public class DecimalDigitsInputFilter implements InputFilter {
private final int decimalDigits;
/**
* Constructor.
*
* #param decimalDigits maximum decimal digits
*/
public DecimalDigitsInputFilter(int decimalDigits) {
this.decimalDigits = decimalDigits;
}
#Override
public CharSequence filter(CharSequence source,
int start,
int end,
Spanned dest,
int dstart,
int dend) {
int dotPos = -1;
int len = dest.length();
for (int i = 0; i < len; i++) {
char c = dest.charAt(i);
if (c == '.' || c == ',') {
dotPos = i;
break;
}
}
if (dotPos >= 0) {
// protects against many dots
if (source.equals(".") || source.equals(","))
{
return "";
}
// if the text is entered before the dot
if (dend <= dotPos) {
return null;
}
if (len - dotPos > decimalDigits) {
return "";
}
}
return null;
}
}
To use:
editText.setFilters(new InputFilter[] {new DecimalDigitsInputFilter(2)});
This implementation of InputFilter solves the problem.
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.DigitsKeyListener;
public class MoneyValueFilter extends DigitsKeyListener {
public MoneyValueFilter() {
super(false, true);
}
private int digits = 2;
public void setDigits(int d) {
digits = d;
}
#Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
CharSequence out = super.filter(source, start, end, dest, dstart, dend);
// if changed, replace the source
if (out != null) {
source = out;
start = 0;
end = out.length();
}
int len = end - start;
// if deleting, source is empty
// and deleting can't break anything
if (len == 0) {
return source;
}
int dlen = dest.length();
// Find the position of the decimal .
for (int i = 0; i < dstart; i++) {
if (dest.charAt(i) == '.') {
// being here means, that a number has
// been inserted after the dot
// check if the amount of digits is right
return (dlen-(i+1) + len > digits) ?
"" :
new SpannableStringBuilder(source, start, end);
}
}
for (int i = start; i < end; ++i) {
if (source.charAt(i) == '.') {
// being here means, dot has been inserted
// check if the amount of digits is right
if ((dlen-dend) + (end-(i + 1)) > digits)
return "";
else
break; // return new SpannableStringBuilder(source, start, end);
}
}
// if the dot is after the inserted part,
// nothing can break
return new SpannableStringBuilder(source, start, end);
}
}
Here is a sample InputFilter which only allows max 4 digits before the decimal point and max 1 digit after that.
Values that edittext allows: 555.2, 555, .2
Values that edittext blocks: 55555.2, 055.2, 555.42
InputFilter filter = new InputFilter() {
final int maxDigitsBeforeDecimalPoint=4;
final int maxDigitsAfterDecimalPoint=1;
#Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
StringBuilder builder = new StringBuilder(dest);
builder.replace(dstart, dend, source
.subSequence(start, end).toString());
if (!builder.toString().matches(
"(([1-9]{1})([0-9]{0,"+(maxDigitsBeforeDecimalPoint-1)+"})?)?(\\.[0-9]{0,"+maxDigitsAfterDecimalPoint+"})?"
)) {
if(source.length()==0)
return dest.subSequence(dstart, dend);
return "";
}
return null;
}
};
mEdittext.setFilters(new InputFilter[] { filter });
I made some fixes for #Pinhassi solution. It handles some cases:
1.you can move cursor anywhere
2.minus sign handling
3.digitsbefore = 2 and digitsafter = 4 and you enter 12.4545. Then if you want to remove ".", it will not allow.
public class DecimalDigitsInputFilter implements InputFilter {
private int mDigitsBeforeZero;
private int mDigitsAfterZero;
private Pattern mPattern;
private static final int DIGITS_BEFORE_ZERO_DEFAULT = 100;
private static final int DIGITS_AFTER_ZERO_DEFAULT = 100;
public DecimalDigitsInputFilter(Integer digitsBeforeZero, Integer digitsAfterZero) {
this.mDigitsBeforeZero = (digitsBeforeZero != null ? digitsBeforeZero : DIGITS_BEFORE_ZERO_DEFAULT);
this.mDigitsAfterZero = (digitsAfterZero != null ? digitsAfterZero : DIGITS_AFTER_ZERO_DEFAULT);
mPattern = Pattern.compile("-?[0-9]{0," + (mDigitsBeforeZero) + "}+((\\.[0-9]{0," + (mDigitsAfterZero)
+ "})?)||(\\.)?");
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
String replacement = source.subSequence(start, end).toString();
String newVal = dest.subSequence(0, dstart).toString() + replacement
+ dest.subSequence(dend, dest.length()).toString();
Matcher matcher = mPattern.matcher(newVal);
if (matcher.matches())
return null;
if (TextUtils.isEmpty(source))
return dest.subSequence(dstart, dend);
else
return "";
}
}
I don't like the other solution and I created my own.
With this solution you can't enter more than MAX_BEFORE_POINT digit before the point and the decimals can't be more than MAX_DECIMAL.
You just can't type the digit in excess, no other effects!
In additional if you write "." it types "0."
Set the EditText in the layout to:
android:inputType="numberDecimal"
Add the Listener in your onCreate. If you want modify the number of digits before and after the point edit the call to PerfectDecimal(str, NUMBER_BEFORE_POINT, NUMBER_DECIMALS), here is set to 3 and 2
EditText targetEditText = (EditText)findViewById(R.id.targetEditTextLayoutId);
targetEditText.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {}
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {}
public void afterTextChanged(Editable arg0) {
String str = targetEditText.getText().toString();
if (str.isEmpty()) return;
String str2 = PerfectDecimal(str, 3, 2);
if (!str2.equals(str)) {
targetEditText.setText(str2);
targetEditText.setSelection(str2.length());
}
}
});
Include this Funcion:
public String PerfectDecimal(String str, int MAX_BEFORE_POINT, int MAX_DECIMAL){
if(str.charAt(0) == '.') str = "0"+str;
int max = str.length();
String rFinal = "";
boolean after = false;
int i = 0, up = 0, decimal = 0; char t;
while(i < max){
t = str.charAt(i);
if(t != '.' && after == false){
up++;
if(up > MAX_BEFORE_POINT) return rFinal;
}else if(t == '.'){
after = true;
}else{
decimal++;
if(decimal > MAX_DECIMAL)
return rFinal;
}
rFinal = rFinal + t;
i++;
}return rFinal;
}
And it's done!
I achieved this with the help of TextWatcher by the following way
final EditText et = (EditText) findViewById(R.id.EditText1);
int count = -1;
et.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence arg0, int arg1, int arg2,int arg3) {
}
public void beforeTextChanged(CharSequence arg0, int arg1,int arg2, int arg3) {
}
public void afterTextChanged(Editable arg0) {
if (arg0.length() > 0) {
String str = et.getText().toString();
et.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL) {
count--;
InputFilter[] fArray = new InputFilter[1];
fArray[0] = new InputFilter.LengthFilter(100);
et.setFilters(fArray);
//change the edittext's maximum length to 100.
//If we didn't change this the edittext's maximum length will
//be number of digits we previously entered.
}
return false;
}
});
char t = str.charAt(arg0.length() - 1);
if (t == '.') {
count = 0;
}
if (count >= 0) {
if (count == 2) {
InputFilter[] fArray = new InputFilter[1];
fArray[0] = new InputFilter.LengthFilter(arg0.length());
et.setFilters(fArray);
//prevent the edittext from accessing digits
//by setting maximum length as total number of digits we typed till now.
}
count++;
}
}
}
});
This solution will not allow the user to enter more than two digit after decimal point. Also you can enter any number of digits before decimal point. I hope this will help. Thank you.
The InputFilter I came up with allows you to configure the number of digits before and after the decimal place. Additionally, it disallows leading zeroes.
public class DecimalDigitsInputFilter implements InputFilter
{
Pattern pattern;
public DecimalDigitsInputFilter(int digitsBeforeDecimal, int digitsAfterDecimal)
{
pattern = Pattern.compile("(([1-9]{1}[0-9]{0," + (digitsBeforeDecimal - 1) + "})?||[0]{1})((\\.[0-9]{0," + digitsAfterDecimal + "})?)||(\\.)?");
}
#Override public CharSequence filter(CharSequence source, int sourceStart, int sourceEnd, Spanned destination, int destinationStart, int destinationEnd)
{
// Remove the string out of destination that is to be replaced.
String newString = destination.toString().substring(0, destinationStart) + destination.toString().substring(destinationEnd, destination.toString().length());
// Add the new string in.
newString = newString.substring(0, destinationStart) + source.toString() + newString.substring(destinationStart, newString.length());
// Now check if the new string is valid.
Matcher matcher = pattern.matcher(newString);
if(matcher.matches())
{
// Returning null indicates that the input is valid.
return null;
}
// Returning the empty string indicates the input is invalid.
return "";
}
}
// To use this InputFilter, attach it to your EditText like so:
final EditText editText = (EditText) findViewById(R.id.editText);
EditText.setFilters(new InputFilter[]{new DecimalDigitsInputFilter(4, 4)});
The requirement is 2 digits after decimal. There should be no limit for digits before decimal point. So, solution should be,
public class DecimalDigitsInputFilter implements InputFilter {
Pattern mPattern;
public DecimalDigitsInputFilter() {
mPattern = Pattern.compile("[0-9]*+((\\.[0-9]?)?)||(\\.)?");
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
Matcher matcher = mPattern.matcher(dest);
if (!matcher.matches())
return "";
return null;
}
}
And use it as,
mEditText.setFilters(new InputFilter[]{new DecimalDigitsInputFilter()});
Thanks to #Pinhassi for the inspiration.
My solution is simple and works perfect!
public class DecimalInputTextWatcher implements TextWatcher {
private String mPreviousValue;
private int mCursorPosition;
private boolean mRestoringPreviousValueFlag;
private int mDigitsAfterZero;
private EditText mEditText;
public DecimalInputTextWatcher(EditText editText, int digitsAfterZero) {
mDigitsAfterZero = digitsAfterZero;
mEditText = editText;
mPreviousValue = "";
mRestoringPreviousValueFlag = false;
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (!mRestoringPreviousValueFlag) {
mPreviousValue = s.toString();
mCursorPosition = mEditText.getSelectionStart();
}
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
if (!mRestoringPreviousValueFlag) {
if (!isValid(s.toString())) {
mRestoringPreviousValueFlag = true;
restorePreviousValue();
}
} else {
mRestoringPreviousValueFlag = false;
}
}
private void restorePreviousValue() {
mEditText.setText(mPreviousValue);
mEditText.setSelection(mCursorPosition);
}
private boolean isValid(String s) {
Pattern patternWithDot = Pattern.compile("[0-9]*((\\.[0-9]{0," + mDigitsAfterZero + "})?)||(\\.)?");
Pattern patternWithComma = Pattern.compile("[0-9]*((,[0-9]{0," + mDigitsAfterZero + "})?)||(,)?");
Matcher matcherDot = patternWithDot.matcher(s);
Matcher matcherComa = patternWithComma.matcher(s);
return matcherDot.matches() || matcherComa.matches();
}
}
Usage:
myTextEdit.addTextChangedListener(new DecimalInputTextWatcher(myTextEdit, 2));
The simplest way to achieve that is:
et.addTextChangedListener(new TextWatcher() {
public void onTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
String text = arg0.toString();
if (text.contains(".") && text.substring(text.indexOf(".") + 1).length() > 2) {
et.setText(text.substring(0, text.length() - 1));
et.setSelection(et.getText().length());
}
}
public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) {
}
public void afterTextChanged(Editable arg0) {
}
});
I have modified the above solutions and created following one. You can set number of digits before and after decimal point.
public class DecimalDigitsInputFilter implements InputFilter {
private final Pattern mPattern;
public DecimalDigitsInputFilter(int digitsBeforeZero, int digitsAfterZero) {
mPattern = Pattern.compile(String.format("[0-9]{0,%d}(\\.[0-9]{0,%d})?", digitsBeforeZero, digitsAfterZero));
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
Matcher matcher = mPattern.matcher(createResultString(source, start, end, dest, dstart, dend));
if (!matcher.matches())
return "";
return null;
}
private String createResultString(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
String sourceString = source.toString();
String destString = dest.toString();
return destString.substring(0, dstart) + sourceString.substring(start, end) + destString.substring(dend);
}
}
Slightly improved #Pinhassi solution.
Works very well. It validates concatenated strings.
public class DecimalDigitsInputFilter implements InputFilter {
Pattern mPattern;
public DecimalDigitsInputFilter() {
mPattern = Pattern.compile("([1-9]{1}[0-9]{0,2}([0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)");
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
String formatedSource = source.subSequence(start, end).toString();
String destPrefix = dest.subSequence(0, dstart).toString();
String destSuffix = dest.subSequence(dend, dest.length()).toString();
String result = destPrefix + formatedSource + destSuffix;
result = result.replace(",", ".");
Matcher matcher = mPattern.matcher(result);
if (matcher.matches()) {
return null;
}
return "";
}
}
Create a new class in Android kotlin with the name DecimalDigitsInputFilter
class DecimalDigitsInputFilter(digitsBeforeDecimal: Int, digitsAfterDecimal: Int) : InputFilter {
var mPattern: Pattern = Pattern.compile("[0-9]{0,$digitsBeforeDecimal}+((\\.[0-9]{0,$digitsAfterDecimal})?)||(\\.)?")
override fun filter(
source: CharSequence?,
start: Int,
end: Int,
dest: Spanned?,
dstart: Int,
dend: Int
): CharSequence? {
val matcher: Matcher = mPattern.matcher(
dest?.subSequence(0, dstart).toString() + source?.subSequence(
start,
end
).toString() + dest?.subSequence(dend, dest.length).toString()
)
if (!matcher.matches())
return ""
else
return null
}
}
Call this class with the following line
et_buy_amount.filters = (arrayOf<InputFilter>(DecimalDigitsInputFilter(8,2)))
there are too many answers for the same but it will allow you to enter 8 digit before decimal and 2 digits after decimal
other answers are accepting only 8 digits
Try using NumberFormat.getCurrencyInstance() to format your string before you put it into a TextView.
Something like:
NumberFormat currency = NumberFormat.getCurrencyInstance();
myTextView.setText(currency.format(dollars));
Edit - There is no inputType for currency that I could find in the docs. I imagine this is because there are some currencies that don't follow the same rule for decimal places, such as the Japanese Yen.
As LeffelMania mentioned, you can correct user input by using the above code with a TextWatcher that is set on your EditText.
DecimalFormat form = new DecimalFormat("#.##", new DecimalFormatSymbols(Locale.US));
EditText et;
et.setOnEditorActionListener(new TextView.OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
double a = Double.parseDouble(et.getText().toString());
et.setText(form.format(a));
}
return false;
}
});
What this does is when you exit editing phase it formats the field to the right format. At them moment it has only 2 decimal charachters. I think this is pretty easy way to do this.
I really liked Pinhassi's answer, but noticed that after the user had entered the specified number digits after the decimal point you could no longer enter text to the left side of the decimal point. The problem was that the solution only tested the previous text that had been entered, not the current text being entered. So here is my solution that inserts the new character into the original text for validation.
package com.test.test;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.text.InputFilter;
import android.text.Spanned;
import android.util.Log;
public class InputFilterCurrency implements InputFilter {
Pattern moPattern;
public InputFilterCurrency(int aiMinorUnits) {
// http://www.regexplanet.com/advanced/java/index.html
moPattern=Pattern.compile("[0-9]*+((\\.[0-9]{0,"+ aiMinorUnits + "})?)||(\\.)?");
} // InputFilterCurrency
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
String lsStart = "";
String lsInsert = "";
String lsEnd = "";
String lsText = "";
Log.d("debug", moPattern.toString());
Log.d("debug", "source: " + source + ", start: " + start + ", end:" + end + ", dest: " + dest + ", dstart: " + dstart + ", dend: " + dend );
lsText = dest.toString();
// If the length is greater then 0, then insert the new character
// into the original text for validation
if (lsText.length() > 0) {
lsStart = lsText.substring(0, dstart);
Log.d("debug", "lsStart : " + lsStart);
// Check to see if they have deleted a character
if (source != "") {
lsInsert = source.toString();
Log.d("debug", "lsInsert: " + lsInsert);
} // if
lsEnd = lsText.substring(dend);
Log.d("debug", "lsEnd : " + lsEnd);
lsText = lsStart + lsInsert + lsEnd;
Log.d("debug", "lsText : " + lsText);
} // if
Matcher loMatcher = moPattern.matcher(lsText);
Log.d("debug", "loMatcher.matches(): " + loMatcher.matches() + ", lsText: " + lsText);
if(!loMatcher.matches()) {
return "";
}
return null;
} // CharSequence
} // InputFilterCurrency
And the call to set the editText filter
editText.setFilters(new InputFilter[] {new InputFilterCurrency(2)});
Ouput with two decimal places
05-22 15:25:33.434: D/debug(30524): [0-9]*+((\.[0-9]{0,2})?)||(\.)?
05-22 15:25:33.434: D/debug(30524): source: 5, start: 0, end:1, dest: 123.4, dstart: 5, dend: 5
05-22 15:25:33.434: D/debug(30524): lsStart : 123.4
05-22 15:25:33.434: D/debug(30524): lsInsert: 5
05-22 15:25:33.434: D/debug(30524): lsEnd :
05-22 15:25:33.434: D/debug(30524): lsText : 123.45
05-22 15:25:33.434: D/debug(30524): loMatcher.matches(): true, lsText: 123.45
Ouput inserting a 5 in the middle
05-22 15:26:17.624: D/debug(30524): [0-9]*+((\.[0-9]{0,2})?)||(\.)?
05-22 15:26:17.624: D/debug(30524): source: 5, start: 0, end:1, dest: 123.45, dstart: 2, dend: 2
05-22 15:26:17.624: D/debug(30524): lsStart : 12
05-22 15:26:17.624: D/debug(30524): lsInsert: 5
05-22 15:26:17.624: D/debug(30524): lsEnd : 3.45
05-22 15:26:17.624: D/debug(30524): lsText : 1253.45
05-22 15:26:17.624: D/debug(30524): loMatcher.matches(): true, lsText: 1253.45
I improved on the solution that uses a regex by Pinhassi so it also handles the edge cases correctly. Before checking if the input is correct, first the final string is constructed as described by the android docs.
public class DecimalDigitsInputFilter implements InputFilter {
private Pattern mPattern;
private static final Pattern mFormatPattern = Pattern.compile("\\d+\\.\\d+");
public DecimalDigitsInputFilter(int digitsBeforeDecimal, int digitsAfterDecimal) {
mPattern = Pattern.compile(
"^\\d{0," + digitsBeforeDecimal + "}([\\.,](\\d{0," + digitsAfterDecimal +
"})?)?$");
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
int dstart, int dend) {
String newString =
dest.toString().substring(0, dstart) + source.toString().substring(start, end)
+ dest.toString().substring(dend, dest.toString().length());
Matcher matcher = mPattern.matcher(newString);
if (!matcher.matches()) {
return "";
}
return null;
}
}
Usage:
editText.setFilters(new InputFilter[] {new DecimalDigitsInputFilter(5,2)});
I have changed answer №6 (by Favas Kv) because there You can put just point in the first position.
final InputFilter [] filter = { new InputFilter() {
#Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
StringBuilder builder = new StringBuilder(dest);
builder.replace(dstart, dend, source
.subSequence(start, end).toString());
if (!builder.toString().matches(
"(([1-9]{1})([0-9]{0,4})?(\\.)?)?([0-9]{0,2})?"
)) {
if(source.length()==0)
return dest.subSequence(dstart, dend);
return "";
}
return null;
}
}};
All answers here are pretty complex I tried to make it much simpler.Look at my code and decide for yourself -
int temp = 0;
int check = 0;
editText.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(editText.getText().toString().length()<temp)
{
if(!editText.getText().toString().contains("."))
editText.setFilters(new InputFilter[] { new InputFilter.LengthFilter(editText.getText().toString().length()-1) });
else
editText.setFilters(new InputFilter[] { new InputFilter.LengthFilter(editText.getText().toString().length()+1) });
}
if(!editText.getText().toString().contains("."))
{
editText.setFilters(new InputFilter[] { new InputFilter.LengthFilter(editText.getText().toString().length()+1) });
check=0;
}
else if(check==0)
{
check=1;
editText.setFilters(new InputFilter[] { new InputFilter.LengthFilter(editText.getText().toString().length()+2) });
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
temp = editText.getText().toString().length();
}
#Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
}
});
Simple Helper class is here to prevent the user entering more than 2 digits after decimal :
public class CostFormatter implements TextWatcher {
private final EditText costEditText;
public CostFormatter(EditText costEditText) {
this.costEditText = costEditText;
}
#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 synchronized void afterTextChanged(final Editable text) {
String cost = text.toString().trim();
if(!cost.endsWith(".") && cost.contains(".")){
String numberBeforeDecimal = cost.split("\\.")[0];
String numberAfterDecimal = cost.split("\\.")[1];
if(numberAfterDecimal.length() > 2){
numberAfterDecimal = numberAfterDecimal.substring(0, 2);
}
cost = numberBeforeDecimal + "." + numberAfterDecimal;
}
costEditText.removeTextChangedListener(this);
costEditText.setText(cost);
costEditText.setSelection(costEditText.getText().toString().trim().length());
costEditText.addTextChangedListener(this);
}
}
Simple BindingAdapter in Kotlin:
#BindingAdapter("maxDecimalPlaces")
fun TextInputEditText.limitDecimalPlaces(maxDecimalPlaces: Int) {
filters += InputFilter { source, _, _, dest, dstart, dend ->
val value = if (source.isEmpty()) {
dest.removeRange(dstart, dend)
} else {
StringBuilder(dest).insert(dstart, source)
}
val matcher = Pattern.compile("([1-9][0-9]*)|([1-9][0-9]*\\.[0-9]{0,$maxDecimalPlaces})|(\\.[0-9]{0,$maxDecimalPlaces})").matcher(value)
if (!matcher.matches()) "" else null
}
}
Like others said, I added this class in my project and set the filter to the EditText I want.
The filter is copied from #Pixel's answer. I'm just putting it all together.
public class DecimalDigitsInputFilter implements InputFilter {
Pattern mPattern;
public DecimalDigitsInputFilter() {
mPattern = Pattern.compile("([1-9]{1}[0-9]{0,2}([0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)");
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
String formatedSource = source.subSequence(start, end).toString();
String destPrefix = dest.subSequence(0, dstart).toString();
String destSuffix = dest.subSequence(dend, dest.length()).toString();
String result = destPrefix + formatedSource + destSuffix;
result = result.replace(",", ".");
Matcher matcher = mPattern.matcher(result);
if (matcher.matches()) {
return null;
}
return "";
}
}
Now set the filter in your EditText like this.
mEditText.setFilters(new InputFilter[]{new DecimalDigitsInputFilter()});
Here one important thing is it does solves my problem of not allowing showing more than two digits after the decimal point in that EditText but the problem is when I getText() from that EditText, it returns the whole input I typed.
For example, after applying the filter over the EditText, I tried to set input 1.5699856987. So in the screen it shows 1.56 which is perfect.
Then I wanted to use this input for some other calculations so I wanted to get the text from that input field (EditText). When I called mEditText.getText().toString() it returns 1.5699856987 which was not acceptable in my case.
So I had to parse the value again after getting it from the EditText.
BigDecimal amount = new BigDecimal(Double.parseDouble(mEditText.getText().toString().trim()))
.setScale(2, RoundingMode.HALF_UP);
setScale does the trick here after getting the full text from the EditText.
Here is my solution:
yourEditText.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
NumberFormat formatter = new DecimalFormat("#.##");
double doubleVal = Double.parseDouble(s.toString());
yourEditText.setText(formatter.format(doubleVal));
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,int after) {}
#Override
public void afterTextChanged(Editable s) {}
});
If the user enters a number with more than two numbers after the decimal point, it will be automatically corrected.
I hope I have helped!
I've also came across this problem. I wanted to be able to reuse the code in many EditTexts. This is my solution:
Usage :
CurrencyFormat watcher = new CurrencyFormat();
priceEditText.addTextChangedListener(watcher);
Class:
public static class CurrencyFormat implements TextWatcher {
public void onTextChanged(CharSequence arg0, int start, int arg2,int arg3) {}
public void beforeTextChanged(CharSequence arg0, int start,int arg2, int arg3) {}
public void afterTextChanged(Editable arg0) {
int length = arg0.length();
if(length>0){
if(nrOfDecimal(arg0.toString())>2)
arg0.delete(length-1, length);
}
}
private int nrOfDecimal(String nr){
int len = nr.length();
int pos = len;
for(int i=0 ; i<len; i++){
if(nr.charAt(i)=='.'){
pos=i+1;
break;
}
}
return len-pos;
}
}
#Meh for u..
txtlist.setFilters(new InputFilter[] { new DigitsKeyListener( Boolean.FALSE,Boolean.TRUE) {
int beforeDecimal = 7;
int afterDecimal = 2;
#Override
public CharSequence filter(CharSequence source, int start, int end,Spanned dest, int dstart, int dend) {
String etText = txtlist.getText().toString();
String temp = txtlist.getText() + source.toString();
if (temp.equals(".")) {
return "0.";
} else if (temp.toString().indexOf(".") == -1) {
// no decimal point placed yet
if (temp.length() > beforeDecimal) {
return "";
}
} else {
int dotPosition ;
int cursorPositon = txtlistprice.getSelectionStart();
if (etText.indexOf(".") == -1) {
dotPosition = temp.indexOf(".");
}else{
dotPosition = etText.indexOf(".");
}
if(cursorPositon <= dotPosition){
String beforeDot = etText.substring(0, dotPosition);
if(beforeDot.length()<beforeDecimal){
return source;
}else{
if(source.toString().equalsIgnoreCase(".")){
return source;
}else{
return "";
}
}
}else{
temp = temp.substring(temp.indexOf(".") + 1);
if (temp.length() > afterDecimal) {
return "";
}
}
}
return super.filter(source, start, end, dest, dstart, dend);
}
} });
A very late response:
We can do it simply like this:
etv.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) {
if (s.toString().length() > 3 && s.toString().contains(".")) {
if (s.toString().length() - s.toString().indexOf(".") > 3) {
etv.setText(s.toString().substring(0, s.length() - 1));
etv.setSelection(edtSendMoney.getText().length());
}
}
}
#Override
public void afterTextChanged(Editable arg0) {
}
}
Here is the TextWatcher that allow only n number of digits after decimal point.
TextWatcher
private static boolean flag;
public static TextWatcher getTextWatcherAllowAfterDeci(final int allowAfterDecimal){
TextWatcher watcher = 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 count,
int after) {
// TODO Auto-generated method stub
}
#Override
public void afterTextChanged(Editable s) {
// TODO Auto-generated method stub
String str = s.toString();
int index = str.indexOf ( "." );
if(index>=0){
if((index+1)<str.length()){
String numberD = str.substring(index+1);
if (numberD.length()!=allowAfterDecimal) {
flag=true;
}else{
flag=false;
}
}else{
flag = false;
}
}else{
flag=false;
}
if(flag)
s.delete(s.length() - 1,
s.length());
}
};
return watcher;
}
How to use
yourEditText.addTextChangedListener(getTextWatcherAllowAfterDeci(1));
This works fine for me. It allows value to be entered even after focus changed and retrieved back. For example: 123.00, 12.12, 0.01, etc..
1.Integer.parseInt(getString(R.string.valuelength))
Specifies the length of the input digits.Values accessed from string.xml file.It is quiet easy to change values.
2.Integer.parseInt(getString(R.string.valuedecimal)), this is for decimal places max limit.
private InputFilter[] valDecimalPlaces;
private ArrayList<EditText> edittextArray;
valDecimalPlaces = new InputFilter[] { new DecimalDigitsInputFilterNew(
Integer.parseInt(getString(R.string.valuelength)),
Integer.parseInt(getString(R.string.valuedecimal)))
};
Array of EditText values that allows to perform action.
for (EditText etDecimalPlace : edittextArray) {
etDecimalPlace.setFilters(valDecimalPlaces);
I just used array of values that contain multiple edittext
Next DecimalDigitsInputFilterNew.class file.
import android.text.InputFilter;
import android.text.Spanned;
public class DecimalDigitsInputFilterNew implements InputFilter {
private final int decimalDigits;
private final int before;
public DecimalDigitsInputFilterNew(int before ,int decimalDigits) {
this.decimalDigits = decimalDigits;
this.before = before;
}
#Override
public CharSequence filter(CharSequence source, int start, int end,
Spanned dest, int dstart, int dend) {
StringBuilder builder = new StringBuilder(dest);
builder.replace(dstart, dend, source
.subSequence(start, end).toString());
if (!builder.toString().matches("(([0-9]{1})([0-9]{0,"+(before-1)+"})?)?(\\.[0-9]{0,"+decimalDigits+"})?")) {
if(source.length()==0)
return dest.subSequence(dstart, dend);
return "";
}
return null;
}
}
This is to build on pinhassi's answer - the issue that I came across was that you couldn't add values before the decimal once the decimal limit has been reached. To fix the issue, we need to construct the final string before doing the pattern match.
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.text.InputFilter;
import android.text.Spanned;
public class DecimalLimiter implements InputFilter
{
Pattern mPattern;
public DecimalLimiter(int digitsBeforeZero,int digitsAfterZero)
{
mPattern=Pattern.compile("[0-9]{0," + (digitsBeforeZero) + "}+((\\.[0-9]{0," + (digitsAfterZero) + "})?)||(\\.)?");
}
#Override
public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend)
{
StringBuilder sb = new StringBuilder(dest);
sb.insert(dstart, source, start, end);
Matcher matcher = mPattern.matcher(sb.toString());
if(!matcher.matches())
return "";
return null;
}
}

App freezes on autocomplete textChanged

I was able to get the first and last text of user input for AutoCompleteTextview but
My App freezes when i set the builder method on AutocompleteTextView textChanged method.
I appreciate any effort provided.
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if(s.length()>0) {
String selectedText = s.toString();
int end = selectedText.length()+start;
SpannableStringBuilder builder = new SpannableStringBuilder(selectedText);
builder.setSpan(android.graphics.Typeface.BOLD, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
Log.i("builderText", " "+builder);
autoCompleteTextView.setText(builder);
}
}
I had a similar issue and fixed it. I had an event called "OnLayoutChage" and added .SetSelection(searchText.Text.Length, searchText.Text.Length); to fix the issue.
"searchText" is AutoCompleteTextView(it is a kind of EditText type)
public void OnLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
searchText.DropDownAnchor = Resource.Id.appbar;
searchText.DropDownWidth = ViewGroup.LayoutParams.MatchParent;
.......
searchText.SetSelection(searchText.Text.Length, searchText.Text.Length);
}
The issue is that you are trying to get the index of a character on a possible empty string. That's why you get -1 here
String startText = autoCompleteTextView.getText().toString();
int start = startText.indexOf(0);
int end = startText.indexOf(1);
You might want to add that statement in a listener e.g. autoCompleteTextView.addTextChangedListener() and handle it there where the actual text changes occur.

Categories