TextView
when the user hides the soft keyboard (ime). It should correct it as a proper money amount: two decimal digits, decimal separator. Of course there is no way on Android to query if the keyboard is going to hide. But there are a few ways, the keyboard can be hidden by the user: backpress when it is visible, focus outside of the TextView, change container TabFragment to other Fragment. This is my solution for this:/** * Simple subclass of an EditText that allows to capture and dispatch BackPress, when the EditText has the focus. * This event is later used to finalise formatted text in the amount field (append trailing ".00" if necessary). * I could not find other way to detect keyboard is about to hide. * Created by: mikinw */ @SuppressWarnings("UnusedDeclaration") public class HwKeySensibleEditText extends EditText { public HwKeySensibleEditText(Context context) { super(context); } public HwKeySensibleEditText(Context context, AttributeSet attrs) { super(context, attrs); } public HwKeySensibleEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { return super.dispatchKeyEvent(event); } @Override public Parcelable onSaveInstanceState() { // This forces a focus changed event to allow {@link AmountReformatter} to do // validation when user hides keyboard onFocusChanged(false, FOCUS_BACKWARD, null); return super.onSaveInstanceState(); } }
This class is responsible to reformat the text inside the TextView. It handles the forced focus changed event also. I didn't delete the references to the outer Constants class, but you can find out, what those values should be.
/** * Controller class for formatting amount field. * Handles all automatic correction that is needed for the amount field. * In the constructor it gets a {@link HwKeySensibleEditText} - which is a slightly modified EditText. * Auto correction has two steps: * - as to user types (add pound sign, don't allow multiple zeros at the beginning, etc. please check * correctOnTheFlyText() function for this. And related unit tests) * - on focus lost (add ".00" as necessary. please check correctFinalAmount() function and related unit tests) * * Also 2 listener can be attached also: OnValueChangedListener and OnEnterLeaveListener. * Created by: mikinw */ public class AmountReformatter implements TextWatcher, View.OnKeyListener, View.OnFocusChangeListener { private HwKeySensibleEditText mAmountTextView; private OnValueChangedListener mValueChangedListener; private OnEnterLeaveListener mEnterLeaveListener; private static final int MAX_INTEGER_DIGITS = 15; public interface OnValueChangedListener { void onValueChanged (String value); } public interface OnEnterLeaveListener { void onEnter(); } private AmountReformatter(HwKeySensibleEditText amountTextView, OnValueChangedListener valueChangedListener, OnEnterLeaveListener enterLeaveListener) { if (amountTextView == null) { throw new IllegalArgumentException("amountTextView can't be null"); } mAmountTextView = amountTextView; mValueChangedListener = valueChangedListener; mEnterLeaveListener = enterLeaveListener; } private boolean mDeleting = false; @Override public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { mDeleting = after < count && mAmountTextView.hasFocus(); } @Override public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) { } @Override public void afterTextChanged(Editable editable) { if(mAmountTextView.hasFocus()){ //update onthefly only if tv has focus, cause correctFinalAmount will be overwrited mAmountTextView.removeTextChangedListener(this); final String curValue = editable.toString(); String corrected = null; if(mDeleting && curValue.length() == 0){ //don't allow user to delete pound char corrected = Constants.POUND_STRING; }else{ corrected = correctOnTheFlyText(curValue); } // prevent infinite recursive call if ( !corrected.equals(editable.toString())) { editable.replace(0, editable.length(), corrected); } mAmountTextView.addTextChangedListener(this); } if (mValueChangedListener != null) { mValueChangedListener.onValueChanged(mAmountTextView.getText().toString()); } } @Override public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.getAction() == KeyEvent.ACTION_UP) { mAmountTextView.clearFocus(); } else if (keyCode == KeyEvent.KEYCODE_ENTER && keyEvent.getAction() == KeyEvent.ACTION_UP) { final InputMethodManager imm = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); mAmountTextView.clearFocus(); return false; } return false; } @Override public void onFocusChange(View view, boolean focusGained) { final String txtAmount = mAmountTextView.getText().toString(); if (focusGained) { if (TextUtils.isEmpty(txtAmount)) { mAmountTextView.setText(Constants.POUND_STRING); }else{ //needs to remove thousands separator mAmountTextView.setText(correctOnTheFlyText(txtAmount)); } //move cursor to the end mAmountTextView.setSelection(mAmountTextView.getText().length()); mAmountTextView.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null); if (mEnterLeaveListener != null) { mEnterLeaveListener.onEnter(); } } else { //restore texthint if there is only pound char mAmountTextView.setText(Constants.POUND_STRING.equals(txtAmount) ? "" : correctFinalAmount(txtAmount)); } } /** * Appends ".00" as needed or truncates to have exactly 2 decimals. Does not check other validity of the original * string (eg. will accept alphaliterals). It it's value is zero, then returns empty string. * * @param original amount string that needs to be modified according to the above rules * @return modified amount text containing decimal separator and two digits after that */ @SuppressWarnings("MagicCharacter") static String correctFinalAmount(String original) { // if no . add . to end // fill the end to have 2 decimals if (TextUtils.isEmpty(original)) { return original; } final String withoutPoundSign = original.charAt(0) == Constants.POUND_CHAR ? original.substring(1) : original; BigDecimal decimal; try { decimal = new BigDecimal(withoutPoundSign); if (decimal.compareTo(new BigDecimal(0)) == 0) { return ""; } } catch (NumberFormatException nfe) { return ""; } decimal = decimal.setScale(2, RoundingMode.DOWN); final DecimalFormat df = new DecimalFormat(); df.setDecimalFormatSymbols(Constants.FORCED_FORMAT_SYMBOLS); df.setGroupingSize(3); df.setGroupingUsed(true); df.setMaximumFractionDigits(2); df.setMinimumFractionDigits(2); df.setPositivePrefix(Constants.POUND_STRING); return df.format(decimal); } /** * Filters out not allowed characters (eg. letters, symbols). Also truncates the leading zeros, but does allow to * have "0.". Also adds pound symbol to the beginning. Truncates ending numbers after the first "." character to * have at most 2 of them. * @param original String that has to be formatted, corrected * @return formatted string according to the rules above */ static String correctOnTheFlyText(String original) { if (TextUtils.isEmpty(original)) { return original; } final StringBuilder preDot = new StringBuilder(original.length() + 1); final StringBuilder postDot = new StringBuilder(original.length() + 1); boolean leadingZero = false; boolean dot = false; int i = 0; final int to = original.length(); for (; i < to; i++) { final char ch = original.charAt(i); if (ch == '0') { leadingZero = true; } else if (ch > '0' && ch <= '9') { break; } else if (ch == Constants.FORCED_FORMAT_SYMBOLS.getDecimalSeparator()) { dot = true; leadingZero = true; break; } } for (; i < to; i++) { final char ch = original.charAt(i); if (ch >= '0' && ch <= '9') { if (preDot.length() < MAX_INTEGER_DIGITS) { preDot.append(ch); } }else if(ch == Constants.FORCED_FORMAT_SYMBOLS.getGroupingSeparator()){ //thousands separator ignor, will be added later } else if (ch == Constants.FORCED_FORMAT_SYMBOLS.getDecimalSeparator()) { dot = true; break; } } for (int decimalCount = 0; i < to && decimalCount < 2; i++) { final char c = original.charAt(i); if (c >= '0' && c <= '9') { postDot.append(c); decimalCount++; } } if (leadingZero && preDot.length() == 0) { preDot.insert(0, '0'); } if (dot) { preDot.append(Constants.FORCED_FORMAT_SYMBOLS.getDecimalSeparator()); } //add thousands separator preDot.append(postDot); preDot.insert(0, Constants.POUND_CHAR); return preDot.toString(); } /** Static initiliser */ public static void setListenersFor(HwKeySensibleEditText amountTextView, OnValueChangedListener valueChangedListener, OnEnterLeaveListener enterListener) { final AmountReformatter reformatter = new AmountReformatter(amountTextView, valueChangedListener, enterListener); amountTextView.setOnFocusChangeListener(reformatter); amountTextView.addTextChangedListener(reformatter); amountTextView.setOnKeyListener(reformatter); } }
No comments:
Post a Comment