Search code examples
androidandroid-edittextcurrency-formatting

How to reposition cursor while editing EditText field which is formatted like US currency- Android


I am formatting edit text field as per US currency format, where while typing number in a field, let's say "12345678" it appears like "12,345,678". For this I have used TextWatcher and on afterTextChanged(...) method I am formatting the entered text like:

@Override
    public void afterTextChanged(Editable editable) {
        String str = editable.toString();
        String number = str.replaceAll("[,]", "");
        if (number.equals(previousNumber) || number.isEmpty()) {
            return;
        }
        previousNumber = number;

        DecimalFormat formatter = new DecimalFormat("#,###.##", new DecimalFormatSymbols(Locale.US));

        String formattedString = formatter.format(number);
        editText.setText(formattedString);
    }

Also, I am using onSelectionChanged(...) callback method like:

 @Override
protected void onSelectionChanged(int selStart, int selEnd) {
    this.setSelection(selStart);
}

But here this 'selStart' doesn't return the actual length of number as it excludes the number of "," in every currency. For example: for "12,345,678" it returns count as 8 instead of 10. That's why I am not able to place my cursor at the end of the field.

Following is the code of custom EditText, which I am using:

public class CurrencyEditText extends AppCompatEditText {
private static final int MAX_LENGTH = 16;
private static final int MAX_DECIMAL_DIGIT = 2;
private static String prefix = "";
private CurrencyTextWatcher currencyTextWatcher = new CurrencyTextWatcher(this, prefix);

public CurrencyEditText(Context context) {
    this(context, null);
}

public CurrencyEditText(Context context, AttributeSet attrs) {
    this(context, attrs, android.support.v7.appcompat.R.attr.editTextStyle);
}

public CurrencyEditText(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    this.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
}

@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
    super.onFocusChanged(focused, direction, previouslyFocusedRect);
    if (focused) {
        this.addTextChangedListener(currencyTextWatcher);
    } else {
        this.removeTextChangedListener(currencyTextWatcher);
    }
    handleCaseCurrencyEmpty(focused);
}

private void handleCaseCurrencyEmpty(boolean focused) {
    if (!focused) {
        if (getText().toString().equals(prefix)) {
            setText("");
        }
    }
}

private static class CurrencyTextWatcher implements TextWatcher {
    private final EditText editText;
    DecimalFormat formatter;
    private String previousNumber;
    private String prefix;
    Context mContext;

    CurrencyTextWatcher(EditText editText, String prefix) {
        this.editText = editText;
        this.prefix = prefix;
        formatter = new DecimalFormat("#,###.##", new DecimalFormatSymbols(Locale.US));
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
    }

    @Override
    public void afterTextChanged(Editable editable) {
        String str = editable.toString();
        String number = str.replaceAll("[,]", "");
        if (number.equals(previousNumber) || number.isEmpty()) {
            return;
        }
        previousNumber = number;

        String formattedString = prefix + formatNumber(number);
        editText.removeTextChangedListener(this);
        editText.setText(formattedString);
        //handleSelection();
        editText.addTextChangedListener(this);
    }

    private String formatNumber(String number) {
        if (number.contains(".")) {
            return formatDecimal(number);
        }
        return formatInteger(number);
    }

    private String formatInteger(String str) {
        BigDecimal parsed = new BigDecimal(str);
        return formatter.format(parsed);
    }

    private String formatDecimal(String str) {
        if (str.equals(".")) {
            return "0.";
        }
        BigDecimal parsed = new BigDecimal(str);
        DecimalFormat formatter = new DecimalFormat("#,##0." + getDecimalPattern(str),
                new DecimalFormatSymbols(Locale.US));
        formatter.setRoundingMode(RoundingMode.DOWN);
        return formatter.format(parsed);
    }

    private String getDecimalPattern(String str) {
        int decimalCount = str.length() - str.indexOf(".") - 1;
        StringBuilder decimalPattern = new StringBuilder();
        for (int i = 0; i < decimalCount && i < MAX_DECIMAL_DIGIT; i++) {
            decimalPattern.append("0");
        }
        return decimalPattern.toString();
    }

    /*private void handleSelection() {
        if (editText.getText().length() <= MAX_LENGTH) {
            editText.setSelection(editText.getText().length());
        }
    }*/
}

@Override
protected void onSelectionChanged(int selStart, int selEnd) {
    this.setSelection(selStart);
}

}

I don't want to use this.setSelection(lengthOfTheEnteredText) because it created issue when you edit the field! What could be the reason that onSelectionChanged(...) does not consider the count of "," present in number?


Solution

  • After exploring more on this issue, I have found the solution. Where I am calculating the cursor position. I have removed onSelectionChanged(...) method from my code and I am handling selection inafterTextChanged(...) method. In the following code I have made changes in afterTextChanged(...) :

    @Override
        public void afterTextChanged(Editable editable) {
    
            String str = editable.toString();
            String number = str.replaceAll("[,]", "");
            if (number.equals(previousNumber) || number.isEmpty()) {
                return;
            }
            previousNumber = number;
    
            int startText, endText;
            startText = editText.getText().length();
    
            int selectionStart = editText.getSelectionStart();
    
            String formattedString = prefix + formatNumber(number);
            editText.removeTextChangedListener(this);
            editText.setText(formattedString);
            endText = editText.getText().length();
    
            int selection = (selectionStart + (endText - startText));
            editText.setSelection(selection);
    
            editText.addTextChangedListener(this);
        }