Change password mask character to asterisk (*), is not difficult, I saw this post. Change EditText password mask character to asterisk (*)
BUT, what I need is, when input the password, for example, input 'a', it will show 'a' at the EditText for one second(default is 1500ms I think). then show the '*'. I need this delay. I can not find any solution. Any help? I check the android source code, it use many internal variable, I can not override it.
You can use this custom password transformation method class
mTextCurrentPin.setTransformationMethod(new
CustomPasswordTransformationMethod());
import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
import android.text.Editable;
import android.text.GetChars;
import android.text.NoCopySpan;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.TransformationMethod;
import android.text.style.UpdateLayout;
import android.view.View;
import java.lang.ref.WeakReference;
public class CustomPasswordTransformationMethod
implements TransformationMethod, TextWatcher {
/* package */ static final Object ACTIVE = new NoCopySpan.Concrete();
/* package */ static final Object CAPPED = new NoCopySpan.Concrete();
/* package */ static final int SHOW_PASSWORD = 8;
private static CustomPasswordTransformationMethod sInstance;
private static char DOT = '\u002A';
public static CustomPasswordTransformationMethod getInstance() {
if (sInstance != null)
return sInstance;
sInstance = new CustomPasswordTransformationMethod();
return sInstance;
}
private static void removeVisibleSpans(Spannable sp) {
Visible[] old = sp.getSpans(0, sp.length(), Visible.class);
for (int i = 0; i < old.length; i++) {
sp.removeSpan(old[i]);
}
}
public CharSequence getTransformation(CharSequence source, View view) {
if (source instanceof Spannable) {
Spannable sp = (Spannable) source;
/*
* Remove any references to other views that may still be
* attached. This will happen when you flip the screen
* while a password field is showing; there will still
* be references to the old EditText in the text.
*/
ViewReference[] vr = sp.getSpans(0, sp.length(),
ViewReference.class);
for (int i = 0; i < vr.length; i++) {
sp.removeSpan(vr[i]);
}
removeVisibleSpans(sp);
sp.setSpan(new ViewReference(view), 0, 0,
Spannable.SPAN_POINT_POINT);
}
return new PasswordCharSequence(source);
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
// This callback isn't used.
}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
if (s instanceof Spannable) {
Spannable sp = (Spannable) s;
ViewReference[] vr = sp.getSpans(0, s.length(),
ViewReference.class);
if (vr.length == 0) {
return;
}
/*
* There should generally only be one ViewReference in the text,
* but make sure to look through all of them if necessary in case
* something strange is going on. (We might still end up with
* multiple ViewReferences if someone moves text from one password
* field to another.)
*/
View v = null;
for (int i = 0; v == null && i < vr.length; i++) {
v = vr[i].get();
}
if (v == null) {
return;
}
removeVisibleSpans(sp);
LogUtil.d("data", "start" + start + " count" + count);
sp.setSpan(new Visible(sp, this), start, start + count,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
/* int pref = TextKeyListener.getInstance().getPrefs(v.getContext());
if ((pref & SHOW_PASSWORD) != 0) {
if (count > 0) {
removeVisibleSpans(sp);
if (count == 1) {
sp.setSpan(new Visible(sp, this), start, start + count,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}*/
}
}
public void afterTextChanged(Editable s) {
// This callback isn't used.
}
public void onFocusChanged(View view, CharSequence sourceText,
boolean focused, int direction,
Rect previouslyFocusedRect) {
if (!focused) {
if (sourceText instanceof Spannable) {
Spannable sp = (Spannable) sourceText;
removeVisibleSpans(sp);
}
}
}
private static class PasswordCharSequence
implements CharSequence, GetChars {
private CharSequence mSource;
public PasswordCharSequence(CharSequence source) {
mSource = source;
}
public int length() {
return mSource.length();
}
public char charAt(int i) {
if (mSource instanceof Spanned) {
Spanned sp = (Spanned) mSource;
int st = sp.getSpanStart(ACTIVE);
int en = sp.getSpanEnd(ACTIVE);
if (i >= st && i < en) {
return mSource.charAt(i);
}
Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);
for (int a = 0; a < visible.length; a++) {
if (sp.getSpanStart(visible[a].mTransformer) >= 0) {
st = sp.getSpanStart(visible[a]);
en = sp.getSpanEnd(visible[a]);
if (i >= st && i < en) {
return mSource.charAt(i);
}
}
}
}
return DOT;
}
public CharSequence subSequence(int start, int end) {
char[] buf = new char[end - start];
getChars(start, end, buf, 0);
return new String(buf);
}
public String toString() {
return subSequence(0, length()).toString();
}
public void getChars(int start, int end, char[] dest, int off) {
TextUtils.getChars(mSource, start, end, dest, off);
int st = -1, en = -1;
int nvisible = 0;
int[] starts = null, ends = null;
if (mSource instanceof Spanned) {
Spanned sp = (Spanned) mSource;
st = sp.getSpanStart(ACTIVE);
en = sp.getSpanEnd(ACTIVE);
Visible[] visible = sp.getSpans(0, sp.length(), Visible.class);
nvisible = visible.length;
starts = new int[nvisible];
ends = new int[nvisible];
for (int i = 0; i < nvisible; i++) {
if (sp.getSpanStart(visible[i].mTransformer) >= 0) {
starts[i] = sp.getSpanStart(visible[i]);
ends[i] = sp.getSpanEnd(visible[i]);
}
}
}
for (int i = start; i < end; i++) {
if (!(i >= st && i < en)) {
boolean visible = false;
for (int a = 0; a < nvisible; a++) {
if (i >= starts[a] && i < ends[a]) {
visible = true;
break;
}
}
if (!visible) {
dest[i - start + off] = DOT;
}
}
}
}
}
private static class Visible
extends Handler
implements UpdateLayout, Runnable {
private Spannable mText;
private CustomPasswordTransformationMethod mTransformer;
public Visible(Spannable sp, CustomPasswordTransformationMethod ptm) {
mText = sp;
mTransformer = ptm;
postAtTime(this, SystemClock.uptimeMillis() + 1000);
}
public void run() {
mText.removeSpan(this);
}
}
/**
* Used to stash a reference back to the View in the Editable so we
* can use it to check the settings.
*/
private static class ViewReference extends WeakReference<View>
implements NoCopySpan {
public ViewReference(View v) {
super(v);
}
}
}
You can use android:inputType="textPassword" in your xml.
Related
I have an activity where credit/debit card details is added. Currently my code allows to add date and month in MM/YY format. But i need year in 4 digits format MM/YYYY. Adding the '/' divider is set after 2nd digit. So if i increase my total digits count, i get the divider after every 2nd digit. How to fix this?
Carddetails.java:
public class CardDetailsActivity extends AppCompatActivity implements TextWatcher {
private static final int CARD_NUMBER_TOTAL_SYMBOLS = 19; // size of pattern 0000-0000-0000-0000
private static final int CARD_NUMBER_TOTAL_DIGITS = 16; // max numbers of digits in pattern: 0000 x walknew2
private static final int CARD_NUMBER_DIVIDER_MODULO = 5; // means divider position is every 5th symbol beginning with walknew4
private static final int CARD_NUMBER_DIVIDER_POSITION = CARD_NUMBER_DIVIDER_MODULO - 1; // means divider position is every 4th symbol beginning with 0
private static final char CARD_NUMBER_DIVIDER = '-';
#BindView(R.id.cardno)
EditText cardno;
#BindView(R.id.cardDateEditText)
EditText cardDateEditText;
#BindView(R.id.cardCVCEditText)
EditText cardCVCEditText;
SharedPreferences pref;
SharedPreferences.Editor editor;
private static final int CARD_DATE_TOTAL_SYMBOLS = 7; // size of pattern MM/YY
private static final int CARD_DATE_TOTAL_DIGITS = 6; // max numbers of digits in pattern: MM + YY
private static final int CARD_DATE_DIVIDER_MODULO = 3; // means divider position is every 3rd symbol beginning with walknew4
private static final int CARD_DATE_DIVIDER_POSITION = CARD_DATE_DIVIDER_MODULO - 1; // means divider position is every 2nd symbol beginning with 0
private static final char CARD_DATE_DIVIDER = '/';
private static final int CARD_CVC_TOTAL_SYMBOLS = 3;
String card_date, card_year, card_month, card_cvv, card_num, user_id, card_type = "debitcard";
#BindView(R.id.btn_add_card)
Button btn_add_card;
#BindView(R.id.btn_back)
Button btn_back;
Context context;
ProgressDialog progressDialog;
ApiService apiService;
String driverid, id, usertype, c_id, booking_id;
EditText card_numm, card_yearm, card_cvvm;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card_details);
ButterKnife.bind(this);
context = CardDetailsActivity.this;
Log.e("TAG", "Inside Card Details Activity:");
apiService = RetrofitSingleton.getApiService();
usertype = PrefConnect.readString(CardDetailsActivity.this, PrefConnect.USERTYPE, "");
pref = getApplicationContext().getSharedPreferences("MyPref", 0);
editor = pref.edit();
id = pref.getString("driver_id", "");
Log.e("driver_id", id);
try {
if (getIntent() != null) {
driverid = getIntent().getStringExtra("Driverid");
booking_id = getIntent().getStringExtra("bookingid");
}
} catch (Exception e) {
e.printStackTrace();
Log.e("paymentact", e.getMessage());
}
cardno.getText().toString().trim();
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
cardno.addTextChangedListener(this);
cardDateEditText.addTextChangedListener(this);
card_numm = (EditText) findViewById(R.id.cardno);
card_yearm = (EditText) findViewById(R.id.cardDateEditText);
card_cvvm = (EditText) findViewById(R.id.cardCVCEditText);
}
#OnClick({R.id.btn_add_card, R.id.btn_back})
public void OnClick(View view) {
switch (view.getId()) {
case R.id.btn_back:
finish();
break;
case R.id.btn_add_card:
View view1 = getCurrentFocus();
Log.e("TAG", "Inside Card Details Activity Button Add Card ");
Validation();
break;
}
}
private boolean isInputCorrect(Editable s, int size, int dividerPosition, char divider) {
boolean isCorrect = s.length() <= size;
for (int i = 0; i < s.length(); i++) {
if (i > 0 && (i + 1) % dividerPosition == 0) {
isCorrect &= divider == s.charAt(i);
} else {
isCorrect &= Character.isDigit(s.charAt(i));
}
}
return isCorrect;
}
private String concatString(char[] digits, int dividerPosition, char divider) {
final StringBuilder formatted = new StringBuilder();
for (int i = 0; i < digits.length; i++) {
if (digits[i] != 0) {
formatted.append(digits[i]);
if ((i > 0) && (i < (digits.length - 1)) && (((i + 1) % dividerPosition) == 0)) {
formatted.append(divider);
}
}
}
return formatted.toString();
}
private char[] getDigitArray(final Editable s, final int size) {
char[] digits = new char[size];
int index = 0;
for (int i = 0; i < s.length() && index < size; i++) {
char current = s.charAt(i);
if (Character.isDigit(current)) {
digits[index] = current;
index++;
}
}
return digits;
}
#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 s) {
card_num = String.valueOf(s);
Log.i("TAG", "Card Date:" + s);
Log.i("TAG", "Card Date :" + cardDateEditText.getEditableText());
if (s == cardno.getEditableText()) {
if (!isInputCorrect(s, CARD_NUMBER_TOTAL_SYMBOLS, CARD_NUMBER_DIVIDER_MODULO, CARD_NUMBER_DIVIDER)) {
s.replace(0, s.length(), concatString(getDigitArray(s, CARD_NUMBER_TOTAL_DIGITS), CARD_NUMBER_DIVIDER_POSITION, CARD_NUMBER_DIVIDER));
}
// DO STH
} else if (s == cardDateEditText.getEditableText()) {
if (!isInputCorrect(s, CARD_DATE_TOTAL_SYMBOLS, CARD_DATE_DIVIDER_MODULO, CARD_DATE_DIVIDER)) {
s.replace(0, s.length(), concatString(getDigitArray(s, CARD_DATE_TOTAL_DIGITS), CARD_DATE_DIVIDER_POSITION, CARD_DATE_DIVIDER));
} else if (s == cardCVCEditText.getEditableText()) {
if (s.length() > CARD_CVC_TOTAL_SYMBOLS) {
s.delete(CARD_CVC_TOTAL_SYMBOLS, s.length());
}
}
}
}
}
As by my understanding, your date divider must always be at position 2
private static final int CARD_DATE_DIVIDER_POSITION = CARD_DATE_DIVIDER_MODULO - 1; // means divider position is every 2nd symbol beginning with 0
with the above declaration, before appending the date divider, I should test this way ( you can change your isInputCorrect method this way):
private boolean isInputCorrect(Editable s, int size, int dividerPosition, char divider) {
boolean isCorrect = s.length() <= size;
for (int i = 0; i < s.length(); i++) {
if (i > 0 && (i + 1) % dividerPosition == 0) {
if (divider=='/') {
if (i==2) {
isCorrect &= divider == s.charAt(i);
}
}
} else {
isCorrect &= Character.isDigit(s.charAt(i));
}
}
return isCorrect;
}
And then after the text has changed can be modified a bit to become:
#Override
public void afterTextChanged(Editable s) {
card_num = String.valueOf(s);
String ss=cardDateEditText.getText().toString();
Log.i("TAG", "Card Date:" + s);
Log.i("TAG", "Card Date :" + ss);
if (s == cardno.getEditableText()) {
if (!isInputCorrect(s, CARD_NUMBER_TOTAL_SYMBOLS, CARD_NUMBER_DIVIDER_MODULO, CARD_NUMBER_DIVIDER)) {
s.replace(0, s.length(), concatString(getDigitArray(s, CARD_NUMBER_TOTAL_DIGITS), CARD_NUMBER_DIVIDER_POSITION, CARD_NUMBER_DIVIDER));
}
} else if (s == cardDateEditText.getEditableText()) {
if (s.length()<=CARD_DATE_TOTAL_SYMBOLS)
{
if (!isInputCorrect(s, CARD_DATE_TOTAL_SYMBOLS, CARD_DATE_DIVIDER_MODULO, CARD_DATE_DIVIDER)) {
Log.e("TAG",s+"");
s.replace(0, s.length(), concatString(getDigitArray(s, CARD_DATE_TOTAL_DIGITS), CARD_DATE_DIVIDER_POSITION, CARD_DATE_DIVIDER));
}
}else
{
s.delete(CARD_DATE_TOTAL_SYMBOLS,s.length());
}
} else if (s == cardCVCEditText.getEditableText()) {
if (s.length() > CARD_CVC_TOTAL_SYMBOLS) {
s.delete(CARD_CVC_TOTAL_SYMBOLS, s.length());
}
}
}
So you test also the length of the editable text.
The date divider must be included only once and just after the month represented by two first characters. That means the last digit must be at 2nd position index 1, at that instant i==1. So no other divider will be appended. I should have to test if the divider set is for date, to know I'm dealing with the date and then test if the index is not greater or equals to 2.
That's how I could handle that. I hope this will help.
How can I mask EditText with below format in android ?
(XXX) XXX-XXXX ext.XXXXXX i.e (654) 321-5846 ext.654321
Used below Custom Edit Text class
package com.test.PhoneNumberFormatter;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v7.widget.AppCompatEditText;
import android.text.Editable;
import android.text.SpannableStringBuilder;
import android.text.TextWatcher;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.TextView;
import com.test.R;
import static android.content.ContentValues.TAG;
public class MaskedEditText extends AppCompatEditText implements TextWatcher {
public static final String SPACE = " ";
private String mask;
private char charRepresentation;
private boolean keepHint;
private int[] rawToMask;
private RawText rawText;
private boolean editingBefore;
private boolean editingOnChanged;
private boolean editingAfter;
private int[] maskToRaw;
private int selection;
private boolean initialized;
private boolean ignore;
protected int maxRawLength;
private int lastValidMaskPosition;
private boolean selectionChanged;
private OnFocusChangeListener focusChangeListener;
private String allowedChars;
private String deniedChars;
public MaskedEditText(Context context) {
super(context);
init();
}
public MaskedEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.MaskedEditText);
mask = attributes.getString(R.styleable.MaskedEditText_mask);
allowedChars = attributes.getString(R.styleable.MaskedEditText_allowed_chars);
deniedChars = attributes.getString(R.styleable.MaskedEditText_denied_chars);
String representation = attributes.getString(R.styleable.MaskedEditText_char_representation);
if (representation == null) {
charRepresentation = '#';
} else {
charRepresentation = representation.charAt(0);
}
keepHint = attributes.getBoolean(R.styleable.MaskedEditText_keep_hint, false);
cleanUp();
// Ignoring enter key presses
setOnEditorActionListener(new OnEditorActionListener() {
#Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
switch (actionId) {
// case EditorInfo.IME_ACTION_NEXT:
// fixing actionNext
// return false;
default:
return true;
}
}
});
attributes.recycle();
}
#Override
public Parcelable onSaveInstanceState() {
final Parcelable superParcellable = super.onSaveInstanceState();
final Bundle state = new Bundle();
state.putParcelable("super", superParcellable);
state.putString("text", getRawText());
state.putBoolean("keepHint", isKeepHint());
return state;
}
#Override
public void onRestoreInstanceState(Parcelable state) {
Bundle bundle = (Bundle) state;
keepHint = bundle.getBoolean("keepHint", false);
super.onRestoreInstanceState(((Bundle) state).getParcelable("super"));
final String text = bundle.getString("text");
setText(text);
Log.d(TAG, "onRestoreInstanceState: " + text);
}
#Override
public void setText(CharSequence text, BufferType type) {
// if (text == null || text.equals("")) return;
super.setText(text, type);
}
/**
* #param listener - its onFocusChange() method will be called before performing MaskedEditText operations,
* related to this event.
*/
#Override
public void setOnFocusChangeListener(OnFocusChangeListener listener) {
focusChangeListener = listener;
}
private void cleanUp() {
initialized = false;
generatePositionArrays();
rawText = new RawText();
selection = rawToMask[0];
editingBefore = true;
editingOnChanged = true;
editingAfter = true;
if (hasHint() && rawText.length() == 0) {
this.setText(makeMaskedTextWithHint());
} else {
this.setText(makeMaskedText());
}
editingBefore = false;
editingOnChanged = false;
editingAfter = false;
maxRawLength = maskToRaw[previousValidPosition(mask.length() - 1)] + 1;
lastValidMaskPosition = findLastValidMaskPosition();
initialized = true;
super.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (focusChangeListener != null) {
focusChangeListener.onFocusChange(v, hasFocus);
}
if (hasFocus()) {
selectionChanged = false;
MaskedEditText.this.setSelection(lastValidPosition());
}
}
});
}
private int findLastValidMaskPosition() {
for (int i = maskToRaw.length - 1; i >= 0; i--) {
if (maskToRaw[i] != -1) return i;
}
throw new RuntimeException("Mask must contain at least one representation char");
}
private boolean hasHint() {
return getHint() != null;
}
public MaskedEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
public void setMask(String mask) {
this.mask = mask;
cleanUp();
}
public String getMask() {
return this.mask;
}
public String getRawText() {
return this.rawText.getText();
}
public void setCharRepresentation(char charRepresentation) {
this.charRepresentation = charRepresentation;
cleanUp();
}
public char getCharRepresentation() {
return this.charRepresentation;
}
/**
* Generates positions for values characters. For instance:
* Input data: mask = "+7(###)###-##-##
* After method execution:
* rawToMask = [3, 4, 5, 6, 8, 9, 11, 12, 14, 15]
* maskToRaw = [-1, -1, -1, 0, 1, 2, -1, 3, 4, 5, -1, 6, 7, -1, 8, 9]
* charsInMask = "+7()- " (and space, yes)
*/
private void generatePositionArrays() {
int[] aux = new int[mask.length()];
maskToRaw = new int[mask.length()];
String charsInMaskAux = "";
int charIndex = 0;
for (int i = 0; i < mask.length(); i++) {
char currentChar = mask.charAt(i);
if (currentChar == charRepresentation) {
aux[charIndex] = i;
maskToRaw[i] = charIndex++;
} else {
String charAsString = Character.toString(currentChar);
if (!charsInMaskAux.contains(charAsString)) {
charsInMaskAux = charsInMaskAux.concat(charAsString);
}
maskToRaw[i] = -1;
}
}
if (charsInMaskAux.indexOf(' ') < 0) {
charsInMaskAux = charsInMaskAux + SPACE;
}
char[] charsInMask = charsInMaskAux.toCharArray();
rawToMask = new int[charIndex];
for (int i = 0; i < charIndex; i++) {
rawToMask[i] = aux[i];
}
}
private void init() {
addTextChangedListener(this);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
if (!editingBefore) {
editingBefore = true;
if (start > lastValidMaskPosition) {
ignore = true;
}
int rangeStart = start;
if (after == 0) {
rangeStart = erasingStart(start);
}
Range range = calculateRange(rangeStart, start + count);
if (range.getStart() != -1) {
rawText.subtractFromString(range);
}
if (count > 0) {
selection = previousValidPosition(start);
}
}
}
private int erasingStart(int start) {
while (start > 0 && maskToRaw[start] == -1) {
start--;
}
return start;
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (!editingOnChanged && editingBefore) {
editingOnChanged = true;
if (ignore) {
return;
}
if (count > 0) {
int startingPosition = maskToRaw[nextValidPosition(start)];
String addedString = s.subSequence(start, start + count).toString();
count = rawText.addToString(clear(addedString), startingPosition, maxRawLength);
if (initialized) {
int currentPosition;
if (startingPosition + count < rawToMask.length)
currentPosition = rawToMask[startingPosition + count];
else
currentPosition = lastValidMaskPosition + 1;
selection = nextValidPosition(currentPosition);
}
}
}
}
#Override
public void afterTextChanged(Editable s) {
if (!editingAfter && editingBefore && editingOnChanged) {
editingAfter = true;
if (hasHint() && (keepHint || rawText.length() == 0)) {
setText(makeMaskedTextWithHint());
} else {
setText(makeMaskedText());
}
selectionChanged = false;
setSelection(selection);
editingBefore = false;
editingOnChanged = false;
editingAfter = false;
ignore = false;
}
}
public boolean isKeepHint() {
return keepHint;
}
public void setKeepHint(boolean keepHint) {
this.keepHint = keepHint;
setText(getRawText());
}
#Override
protected void onSelectionChanged(int selStart, int selEnd) {
// On Android 4+ this method is being called more than 1 time if there is a hint in the EditText, what moves the cursor to left
// Using the boolean var selectionChanged to limit to one execution
if (initialized) {
if (!selectionChanged) {
selStart = fixSelection(selStart);
selEnd = fixSelection(selEnd);
// exactly in this order. If getText.length() == 0 then selStart will be -1
if (selStart > getText().length()) selStart = getText().length();
if (selStart < 0) selStart = 0;
// exactly in this order. If getText.length() == 0 then selEnd will be -1
if (selEnd > getText().length()) selEnd = getText().length();
if (selEnd < 0) selEnd = 0;
setSelection(selStart, selEnd);
selectionChanged = true;
} else {
//check to see if the current selection is outside the already entered text
if (selStart > rawText.length() - 1) {
final int start = fixSelection(selStart);
final int end = fixSelection(selEnd);
if (start >= 0 && end < getText().length()) {
setSelection(start, end);
}
}
}
}
super.onSelectionChanged(selStart, selEnd);
}
private int fixSelection(int selection) {
if (selection > lastValidPosition()) {
return lastValidPosition();
} else {
return nextValidPosition(selection);
}
}
private int nextValidPosition(int currentPosition) {
while (currentPosition < lastValidMaskPosition && maskToRaw[currentPosition] == -1) {
currentPosition++;
}
if (currentPosition > lastValidMaskPosition) return lastValidMaskPosition + 1;
return currentPosition;
}
private int previousValidPosition(int currentPosition) {
while (currentPosition >= 0 && maskToRaw[currentPosition] == -1) {
currentPosition--;
if (currentPosition < 0) {
return nextValidPosition(0);
}
}
return currentPosition;
}
private int lastValidPosition() {
if (rawText.length() == maxRawLength) {
return rawToMask[rawText.length() - 1] + 1;
}
return nextValidPosition(rawToMask[rawText.length()]);
}
private String makeMaskedText() {
int maskedTextLength;
if (rawText.length() < rawToMask.length) {
maskedTextLength = rawToMask[rawText.length()];
} else {
maskedTextLength = mask.length();
}
char[] maskedText = new char[maskedTextLength]; //mask.replace(charRepresentation, ' ').toCharArray();
for (int i = 0; i < maskedText.length; i++) {
int rawIndex = maskToRaw[i];
if (rawIndex == -1) {
maskedText[i] = mask.charAt(i);
} else {
maskedText[i] = rawText.charAt(rawIndex);
}
}
return new String(maskedText);
}
private CharSequence makeMaskedTextWithHint() {
SpannableStringBuilder ssb = new SpannableStringBuilder();
int mtrv;
int maskFirstChunkEnd = rawToMask[0];
for (int i = 0; i < mask.length(); i++) {
mtrv = maskToRaw[i];
if (mtrv != -1) {
if (mtrv < rawText.length()) {
ssb.append(rawText.charAt(mtrv));
} else {
ssb.append(getHint().charAt(maskToRaw[i]));
}
} else {
ssb.append(mask.charAt(i));
}
if ((keepHint && rawText.length() < rawToMask.length && i >= rawToMask[rawText.length()])
|| (!keepHint && i >= maskFirstChunkEnd)) {
ssb.setSpan(new ForegroundColorSpan(getCurrentHintTextColor()), i, i + 1, 0);
}
}
return ssb;
}
private Range calculateRange(int start, int end) {
Range range = new Range();
for (int i = start; i <= end && i < mask.length(); i++) {
if (maskToRaw[i] != -1) {
if (range.getStart() == -1) {
range.setStart(maskToRaw[i]);
}
range.setEnd(maskToRaw[i]);
}
}
if (end == mask.length()) {
range.setEnd(rawText.length());
}
if (range.getStart() == range.getEnd() && start < end) {
int newStart = previousValidPosition(range.getStart() - 1);
if (newStart < range.getStart()) {
range.setStart(newStart);
}
}
return range;
}
private String clear(String string) {
if (deniedChars != null) {
for (char c : deniedChars.toCharArray()) {
string = string.replace(Character.toString(c), "");
}
}
if (allowedChars != null) {
StringBuilder builder = new StringBuilder(string.length());
for (char c : string.toCharArray()) {
if (allowedChars.contains(String.valueOf(c))) {
builder.append(c);
}
}
string = builder.toString();
}
return string;
}
}
In activity_main.xml
<com.test.PhoneNumberFormatter.MaskedEditText
android:id="#+id/phone_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:hint="#string/phone_number"
android:imeOptions="actionNext"
android:inputType="textPhonetic"
android:paddingBottom="#dimen/_3sdp"
android:paddingTop="#dimen/_8sdp"
android:textColorHint="#color/grey_color"
android:textSize="#dimen/_12sdp"
mask:allowed_chars="1234567890ext."
mask:keep_hint="false"
mask:mask="(###)###-#### ext.######" />
Thanks.
For format Edit-text, below things can be helpful to you.
**Step 1** : Add following line in your build.gradle file
implementation 'com.github.pinball83:masked-edittext:1.0.4'
**Step 2 :** by xml :
<com.github.pinball83.maskededittext.MaskedEditText
android:id="#+id/masked_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="number"
app:mask="8 (***) *** **-**"
app:notMaskedSymbol="*"
app:maskIcon="#drawable/abc_ic_clear_mtrl_alpha"
app:maskIconColor="#color/colorPrimary"
/>
or by java file :
MaskedEditText maskedEditText = new MaskedEditText.Builder(context)
.mask("8 (***) *** **-**")
.notMaskedSymbol("*")
.icon(R.drawable.ic_account_circle)
.iconCallback(unmaskedText -> { //Icon click callback handler })
.build();
MaskedEditText editText = new MaskedEditText.Builder(context)
.mask("8 (***) *** **-**")
.notMaskedSymbol("*")
.build();; //set mask to "8 (***) *** **-**" and not masked symbol to "*"
Text setup and formatting
MaskedEditText editText = new MaskedEditText..Builder(context)
.mask("8 (***) *** **-**")
.notMaskedSymbol("*")
.format("[1][2][3] [4][5][6]-[7][8]-[10][9]")//set format of returned data input into MaskedEditText
.build();
editText.setMaskedText("5551235567");
for more details use following url :
https://github.com/pinball83/Masked-Edittext
Enjoy :)
Step 1 : Create editText
<EditText
android:id="#+id/masked_edit_text"
android:layout_width="yourSize"
android:layout_height="yourSize"
android:inputType="textPassword"/>
Step2:
public class CustomTransformation extends PasswordTransformationMethod {
#Override
public CharSequence getTransformation(CharSequence source, View view) {
return new TransformedSequence(source);
}
private class TransformedSequence implements CharSequence {
private CharSequence sequence;
public TransformedSequence(CharSequence source) {
sequence = source; // Store char sequence
}
public char charAt(int index) {
return 'X'; // Replace with what you want.
}
public int length() {
return sequence.length(); // return the length
}
public CharSequence subSequence(int start, int end) {
return sequence.subSequence(start, end); //return sequence
}
}
Step 3: apply the transformation
EditText edittext = findViewById(R.id.masked_edit_text);
edittext.setTransformationMethod(new CustomTransformation());
https://github.com/mukeshsolanki/country-picker-android
try this library .
this will automatically mask your edittext with selected country number .
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());
The requirement is to limit user from not entering more than 1 decimal value in a numeric/decimal edit text field. That said, I also need to limit the number entry to 6 Max digits. eg. 999999.9
If a user enters numeric alone - then I should be able to limit the user to 6 digits Max, but should allow "." and decimal number(if entered by the user).
I am not sure, how to do this. Any help and reference will be of great help.
Maybe some implementation similar to this? I'm pretty sure it can be optimized a lot!
EditText et;
.....
// force number input type on edittext
et.setRawInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
et.addTextChangedListener(new CustomTextWatcher(et));
where:
class CustomTextWatcher implements TextWatcher {
private NumberFormat nf = NumberFormat.getNumberInstance();
private EditText et;
private String tmp = "";
private int moveCaretTo;
private static final int INTEGER_CONSTRAINT = 6;
private static final int FRACTION_CONSTRAINT = 1;
private static final int MAX_LENGTH = INTEGER_CONSTRAINT + FRACTION_CONSTRAINT + 1;
public CustomTextWatcher(EditText et) {
this.et = et;
nf.setMaximumIntegerDigits(INTEGER_CONSTRAINT);
nf.setMaximumFractionDigits(FRACTION_CONSTRAINT);
nf.setGroupingUsed(false);
}
public int countOccurrences(String str, char c) {
int count = 0;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == c) {
count++;
}
}
return count;
}
#Override
public void afterTextChanged(Editable s) {
et.removeTextChangedListener(this); // remove to prevent stackoverflow
String ss = s.toString();
int len = ss.length();
int dots = countOccurrences(ss, '.');
boolean shouldParse = dots <= 1 && (dots == 0 ? len != (INTEGER_CONSTRAINT + 1) : len < (MAX_LENGTH + 1));
if (shouldParse) {
if (len > 1 && ss.lastIndexOf(".") != len - 1) {
try {
Double d = Double.parseDouble(ss);
if (d != null) {
et.setText(nf.format(d));
}
} catch (NumberFormatException e) {
}
}
} else {
et.setText(tmp);
}
et.addTextChangedListener(this); // reset listener
//tried to fix caret positioning after key type:
if (et.getText().toString().length() > 0) {
if (dots == 0 && len >= INTEGER_CONSTRAINT && moveCaretTo > INTEGER_CONSTRAINT) {
moveCaretTo = INTEGER_CONSTRAINT;
} else if (dots > 0 && len >= (MAX_LENGTH) && moveCaretTo > (MAX_LENGTH)) {
moveCaretTo = MAX_LENGTH;
}
try {
et.setSelection(et.getText().toString().length());
// et.setSelection(moveCaretTo); <- almost had it :))
} catch (Exception e) {
}
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
moveCaretTo = et.getSelectionEnd();
tmp = s.toString();
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
int length = et.getText().toString().length();
if (length > 0) {
moveCaretTo = start + count - before;
}
}
}
Not 100% but you can use as a base and build on top of it ;)
EDIT: tried to polishing setting the caret position after text changed but it was more difficult than I estimated and reverted to setting the caret at the end after each char input. I left the code I started on for the caret maybe you can improve it?
There is a small mistake in above answer
I edited that one use the below code.
import java.math.RoundingMode;
import java.text.NumberFormat;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
public class DecimalTextWatcher implements TextWatcher {
private NumberFormat numberFormat = NumberFormat.getNumberInstance();
private EditText editText;
private String temp = "";
private int moveCaretTo;
private int integerConstraint;
private int fractionConstraint;
private int maxLength;
/**
* Add a text watcher to Edit text for decimal formats
*
* #param editText
* EditText to add DecimalTextWatcher
* #param before
* digits before decimal point
* #param after
* digits after decimal point
*/
public DecimalTextWatcher(EditText editText, int before, int after) {
this.editText = editText;
this.integerConstraint = before;
this.fractionConstraint = after;
this.maxLength = before + after + 1;
numberFormat.setMaximumIntegerDigits(integerConstraint);
numberFormat.setMaximumFractionDigits(fractionConstraint);
numberFormat.setRoundingMode(RoundingMode.DOWN);
numberFormat.setGroupingUsed(false);
}
private int countOccurrences(String str, char c) {
int count = 0;
for (int i = 0; i < str.length(); i++) {
if (str.charAt(i) == c) {
count++;
}
}
return count;
}
#Override
public void afterTextChanged(Editable s) {
// remove to prevent StackOverFlowException
editText.removeTextChangedListener(this);
String ss = s.toString();
int len = ss.length();
int dots = countOccurrences(ss, '.');
boolean shouldParse = dots <= 1 && (dots == 0 ? len != (integerConstraint + 1) : len < (maxLength + 1));
boolean x = false;
if (dots == 1) {
int indexOf = ss.indexOf('.');
try {
if (ss.charAt(indexOf + 1) == '0') {
shouldParse = false;
x = true;
if (ss.substring(indexOf).length() > 2) {
shouldParse = true;
x = false;
}
}
} catch (Exception ex) {
}
}
if (shouldParse) {
if (len > 1 && ss.lastIndexOf(".") != len - 1) {
try {
Double d = Double.parseDouble(ss);
if (d != null) {
editText.setText(numberFormat.format(d));
}
} catch (NumberFormatException e) {
}
}
} else {
if (x) {
editText.setText(ss);
} else {
editText.setText(temp);
}
}
editText.addTextChangedListener(this); // reset listener
// tried to fix caret positioning after key type:
if (editText.getText().toString().length() > 0) {
if (dots == 0 && len >= integerConstraint && moveCaretTo > integerConstraint) {
moveCaretTo = integerConstraint;
} else if (dots > 0 && len >= (maxLength) && moveCaretTo > (maxLength)) {
moveCaretTo = maxLength;
}
try {
editText.setSelection(editText.getText().toString().length());
// et.setSelection(moveCaretTo); <- almost had it :))
} catch (Exception e) {
}
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
moveCaretTo = editText.getSelectionEnd();
temp = s.toString();
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
int length = editText.getText().toString().length();
if (length > 0) {
moveCaretTo = start + count - before;
}
}
}
use it as below..
itemCostEditText.addTextChangedListener(new DecimalTextWatcher(itemCostEditText, 6, 2));
You can make use of TextWatcher's afterTextChanged/onTextChanged methods to get notified for text changes and DecimalFormat to format input text
http://developer.android.com/reference/java/text/DecimalFormat.html
http://developer.android.com/reference/android/text/TextWatcher.html
I have an EditText in which the user should input a number including decimals and i want a thousand separator automatically added onto the input number I tried a couple of other methods but some do not allow floating point numbers so i came up with this code which works well only that the string input is not being edited in realtime to one with possible thousand separators and the errors seem to stem from the s.replace();
am2 = new TextWatcher(){
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
public void afterTextChanged(Editable s) {
if (s.toString().equals("")) {
amount.setText("");
value = 0;
}else{
StringBuffer strBuff = new StringBuffer();
char c;
for (int i = 0; i < amount2.getText().toString().length() ; i++) {
c = amount2.getText().toString().charAt(i);
if (Character.isDigit(c)) {
strBuff.append(c);
}
}
value = Double.parseDouble(strBuff.toString());
reverse();
NumberFormat nf2 = NumberFormat.getInstance(Locale.ENGLISH);
((DecimalFormat)nf2).applyPattern("###,###.#######");
s.replace(0, s.length(), nf2.format(value));
}
}
};
This Class solves the problem, allows decimal input and adds the thousand separators.
public class NumberTextWatcher implements TextWatcher {
private DecimalFormat df;
private DecimalFormat dfnd;
private boolean hasFractionalPart;
private EditText et;
public NumberTextWatcher(EditText et)
{
df = new DecimalFormat("#,###.##");
df.setDecimalSeparatorAlwaysShown(true);
dfnd = new DecimalFormat("#,###");
this.et = et;
hasFractionalPart = false;
}
#SuppressWarnings("unused")
private static final String TAG = "NumberTextWatcher";
public void afterTextChanged(Editable s)
{
et.removeTextChangedListener(this);
try {
int inilen, endlen;
inilen = et.getText().length();
String v = s.toString().replace(String.valueOf(df.getDecimalFormatSymbols().getGroupingSeparator()), "");
Number n = df.parse(v);
int cp = et.getSelectionStart();
if (hasFractionalPart) {
et.setText(df.format(n));
} else {
et.setText(dfnd.format(n));
}
endlen = et.getText().length();
int sel = (cp + (endlen - inilen));
if (sel > 0 && sel <= et.getText().length()) {
et.setSelection(sel);
} else {
// place cursor at the end?
et.setSelection(et.getText().length() - 1);
}
} catch (NumberFormatException nfe) {
// do nothing?
} catch (ParseException e) {
// do nothing?
}
et.addTextChangedListener(this);
}
public void beforeTextChanged(CharSequence s, int start, int count, int after)
{
}
public void onTextChanged(CharSequence s, int start, int before, int count)
{
if (s.toString().contains(String.valueOf(df.getDecimalFormatSymbols().getDecimalSeparator())))
{
hasFractionalPart = true;
} else {
hasFractionalPart = false;
}
}
}
Source: http://blog.roshka.com/2012/08/android-edittext-with-number-format.html
Unfortunately the code did not work as it is in the answer.
It has two problems:
It does not work if the phone locale configuration uses "," as a decimal separator.
It does not work if the number has trailing zeros in the decimal part. Example 1.01.
I went crazy to fix it.
Finally I came to this code that worked well on my cell phone:
NumberTextWatcher.java
import android.text.Editable;
import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
import android.util.Log;
import android.widget.EditText;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.util.Locale;
public class NumberTextWatcher
implements TextWatcher {
private static final String TAG = "NumberTextWatcher";
private final int numDecimals;
private String groupingSep;
private String decimalSep;
private boolean nonUsFormat;
private DecimalFormat df;
private DecimalFormat dfnd;
private boolean hasFractionalPart;
private EditText et;
private String value;
private String replicate(char ch, int n) {
return new String(new char[n]).replace("\0", "" + ch);
}
public NumberTextWatcher(EditText et, Locale locale, int numDecimals) {
et.setKeyListener(DigitsKeyListener.getInstance("0123456789.,"));
this.numDecimals = numDecimals;
DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);
char gs = symbols.getGroupingSeparator();
char ds = symbols.getDecimalSeparator();
groupingSep = String.valueOf(gs);
decimalSep = String.valueOf(ds);
String patternInt = "#,###";
dfnd = new DecimalFormat(patternInt, symbols);
String patternDec = patternInt + "." + replicate('#', numDecimals);
df = new DecimalFormat(patternDec, symbols);
df.setDecimalSeparatorAlwaysShown(true);
df.setRoundingMode(RoundingMode.DOWN);
this.et = et;
hasFractionalPart = false;
nonUsFormat = !decimalSep.equals(".");
value = null;
}
#Override
public void afterTextChanged(Editable s) {
Log.d(TAG, "afterTextChanged");
et.removeTextChangedListener(this);
try {
int inilen, endlen;
inilen = et.getText().length();
String v = value.replace(groupingSep, "");
Number n = df.parse(v);
int cp = et.getSelectionStart();
if (hasFractionalPart) {
int decPos = v.indexOf(decimalSep) + 1;
int decLen = v.length() - decPos;
if (decLen > numDecimals) {
v = v.substring(0, decPos + numDecimals);
}
int trz = countTrailingZeros(v);
StringBuilder fmt = new StringBuilder(df.format(n));
while (trz-- > 0) {
fmt.append("0");
}
et.setText(fmt.toString());
} else {
et.setText(dfnd.format(n));
}
endlen = et.getText().length();
int sel = (cp + (endlen - inilen));
if (sel > 0 && sel <= et.getText().length()) {
et.setSelection(sel);
} else {
// place cursor at the end?
et.setSelection(et.getText().length() - 1);
}
} catch (NumberFormatException | ParseException nfe) {
// do nothing?
}
et.addTextChangedListener(this);
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
Log.d(TAG, "beforeTextChanged");
value = et.getText().toString();
}
private int countTrailingZeros(String str) {
int count = 0;
for (int i = str.length() - 1; i >= 0; i--) {
char ch = str.charAt(i);
if ('0' == ch) {
count++;
} else {
break;
}
}
return count;
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Log.d(TAG, "onTextChanged");
String newValue = s.toString();
String change = newValue.substring(start, start + count);
String prefix = value.substring(0, start);
String suffix = value.substring(start + before);
if (".".equals(change) && nonUsFormat) {
change = decimalSep;
}
value = prefix + change + suffix;
hasFractionalPart = value.contains(decimalSep);
Log.d(TAG, "VALUE: " + value);
}
}
and then to use it simply to do:
Locale locale = new Locale("es", "AR"); // For example Argentina
int numDecs = 2; // Let's use 2 decimals
TextWatcher tw = new NumberTextWatcher(myEditText, locale, numDecs);
myEditText.addTextChangedListener(tw);
You need to use DecimalFormat class with DecimalFormatSymbols class, check the out following method,
public static String formatAmount(int num)
{
DecimalFormat decimalFormat = new DecimalFormat();
DecimalFormatSymbols decimalFormateSymbol = new DecimalFormatSymbols();
decimalFormateSymbol.setGroupingSeparator(',');
decimalFormat.setDecimalFormatSymbols(decimalFormateSymbol);
return decimalFormat.format(num);
}
you can use kotlin extensions function like this...
fun EditText.onCommaChange(input: (String) -> Unit) {
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
if (!edit) {
edit = true
if (s.toString() != "₹") {
try {
val flNumber = getCommaLessNumber(s.toString()).toInt()
val fNumber = getFormattedAmount(flNumber)
setText(fNumber)
setSelection(text.length)
input(flNumber.toString())
} catch (e: NumberFormatException) {
Timber.e(e)
}
} else {
setText("")
input("")
}
edit = false
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})}
fun getCommaLessNumber(commaNumber: String): String {
var number = commaNumber.replace("₹", "")
number = number.replace(",".toRegex(), "")
return number}
fun getFormattedAmount(amount: Int): String {
return "₹${String.format("%,d", amount)}"}
fun EditText.text() = this.text.toString()
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
import java.text.DecimalFormat;
public class MyNumberWatcher_3Digit implements TextWatcher {
private EditText editText;
private int digit;
public MyNumberWatcher_3Digit(EditText editText) {
this.editText = 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) {
editText.removeTextChangedListener( this );
String s = editText.getText().toString();
s = s.replace( ",", "" ).replace( "٬", "" );
s = replaceNonstandardDigits( s );
if (s.length() > 0) {
DecimalFormat sdd = new DecimalFormat( "#,###" );
Double doubleNumber = Double.parseDouble( s );
String format = sdd.format( doubleNumber );
editText.setText( format );
editText.setSelection( format.length() );
}
editText.addTextChangedListener( this );
}
static String replaceNonstandardDigits(String input) {
if (input == null || input.isEmpty()) {
return input;
}
StringBuilder builder = new StringBuilder();
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt( i );
if (isNonstandardDigit( ch )) {
int numericValue = Character.getNumericValue( ch );
if (numericValue >= 0) {
builder.append( numericValue );
}
} else {
builder.append( ch );
}
}
return builder.toString();
}
private static boolean isNonstandardDigit(char ch) {
return Character.isDigit( ch ) && !(ch >= '0' && ch <= '9');
}
}
// oncreate activity
input_text_rate.addTextChangedListener(new MyNumberWatcher_3Digit(input_text_rate));
I used this way in Kotlin for a Dialog:
val et = dialog.findViewById(R.id.etNumber) as EditText
et.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable) {
et.removeTextChangedListener(this)
forChanged(et)
et.addTextChangedListener(this)
}
override fun beforeTextChanged(
s: CharSequence,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
}
})
then write a method like this:
private fun forChanged(alpha: EditText) {
val string = alpha.text.toString()
val dec = DecimalFormat("#,###")
if (!TextUtils.isEmpty(string)) {
val textWC = string.replace(",".toRegex(), "")
val number = textWC.toDouble()
alpha.setText(dec.format(number))
alpha.setSelection(dec.format(number).length)
}
}
I have tried solutions but with ending 0 i was having problem, sometimes user just wanted to enter 0.01 or 0.0001,
I don't know if any other have posted same answer or not but if this helps,
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.widget.EditText;
import java.text.DecimalFormat;
public class NumberTextWatcher implements TextWatcher {
private DecimalFormat dfnd;
private boolean hasFractionalPart;
private EditText inputView;
public static void bindView(EditText inputView) {
NumberTextWatcher temp = new NumberTextWatcher(inputView);
inputView.addTextChangedListener(temp);
}
public NumberTextWatcher(EditText inputView) {
dfnd = new DecimalFormat("#,###.######");
this.inputView = inputView;
hasFractionalPart = false;
}
#SuppressWarnings("unused")
private static final String TAG = "NumberTextWatcher";
public void afterTextChanged(Editable s) {
Log.d(TAG, "afterTextChanged() called with: s = [" + s + "]");
inputView.removeTextChangedListener(this);
try {
String text = inputView.getText().toString().replace(String.valueOf(dfnd.getDecimalFormatSymbols().getGroupingSeparator()), "");
if(text.charAt(text.length() - 1) == '.')
{
if(getCount(text,'.') >1)
{
text = text.substring(0,text.length()-1);
}
}
String afterDecimalPoint = "";
String beforeDecimalPoint = text;
if (hasFractionalPart || (text.charAt(text.length() - 1) == '0')) {
String[] data = text.split("\\.");
beforeDecimalPoint = data[0];
if (data.length != 2) {
afterDecimalPoint = ".";
} else {
afterDecimalPoint = "." + data[1];
if (data[1].length() >= dfnd.getMaximumFractionDigits()) {
afterDecimalPoint = "." + data[1].substring(0, dfnd.getMaximumFractionDigits());
}
}
}
beforeDecimalPoint = dfnd.format(Double.parseDouble(beforeDecimalPoint));
String finalText = beforeDecimalPoint;
if (hasFractionalPart) {
finalText = beforeDecimalPoint + afterDecimalPoint;
}
inputView.setText(finalText);
inputView.setSelection(finalText.length());
} catch (Exception nfe) {
// do nothing?
nfe.printStackTrace();
}
inputView.addTextChangedListener(this);
}
private int getCount(String someString, char someChar) {
int count = 0;
for (int i = 0; i < someString.length(); i++) {
if (someString.charAt(i) == someChar) {
count++;
}
}
return count;
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (s.toString().contains(String.valueOf(dfnd.getDecimalFormatSymbols().getDecimalSeparator()))) {
hasFractionalPart = true;
} else {
hasFractionalPart = false;
}
}
}