Подтвердить что ты не робот

Android EditText, проблема с клавиатурой textWatcher

Я работаю над Android-приложением, и у меня есть EditText, где пользователь может вводить числа. Я хочу отформатировать номер, используя разные форматы валют (например, ##, ##, ###), и я хочу сделать это "на лету", т.е. когда пользователь вводит каждую цифру (а не при нажатии на ввод). Я googled вокруг, и наткнулся TextWatcher, который я впервые нашел многообещающим, но это оказалось абсолютной болью. Я отлаживаю свой код на телефоне HTC Desire, который имеет только мягкую клавиатуру.

Теперь я хочу получить обратный вызов, когда пользователь нажимает цифры (от 0 до 9), del (backspace) и вводит ключ. Из моего тестирования я нашел их (по крайней мере, на моем телефоне)

1) editText onKeyListener вызывается когда пользователь нажимает del или вводит ключ. Когда пользователь нажимает enter, onKey функция вызывается дважды для одного входа (который я считаю для ACTION_UP и ACTION_DOWN). Когда пользователь нажимает del, onKey вызывается один раз (только для ACTION_DOWN), который я не знаю, почему. onKey никогда не вызывается, когда пользователь нажимает любые цифры (от 0 до 9), которые тоже я не могу понять.

2) Функции обратного вызова TextWatchers 3 (beforeTextChanged, onTextChanged, afterTextChanged) всякий раз, когда пользователь нажимает любое число (от 0 до 9). Поэтому я подумал, используя TextWatcher и onKeyListener вместе Я могу получить все обратные вызовы, которые мне нужны.

Теперь мои вопросы таковы.

1) Сначала на моей мягкой клавиатуре HTC там является ключом (символ клавиатуры с стрелка вниз), и когда я нажимаю на нее клавиатура ушла в отставку, не давая любой обратный вызов. Я все еще не верю андроид, позволяющий пользователю редактировать поле и уйти в отставку, не позволяя программе обработать (сохранить) редактирование. Теперь мой editText показывает одно значение, а мое объект имеет другое значение (я сохраняю редактирование пользователя при вводе и обратная обработка нажмите на клавиатуру, перейдя по ссылке Значение editText со значением в объект, но у меня нет ответа на этот вопрос клавиатура вниз).

2) Во-вторых, я хочу отформатировать номер после ввода пользователем новой цифры. Сказать У меня уже есть 123 на editText и введенный пользователем 4, я хочу, чтобы editText для отображения 1,234. Я получаю полный число на onTextChanged() и afterTextChanged() и я могу форматировать номер и вернуть его обратно editText в любом из этих обратных вызовов. Какой из них я должен использовать? Какой лучшая практика?

3) Третий - самый важный проблема. Когда приложение запускается, я текущее значение объекта в editText. Скажем, я положил 123 на onResume(), и когда пользователь вводит цифру (скажем, 4) Я хочу должно быть 1234. Но на моем onTextChanged обратный вызов, что я получаю, - 4123. Когда Я нажимаю еще один ключ (скажем, 5) Я получив 45123. Таким образом, для пользовательских входов Курсор editText указывает на конец текст. Но когда значение задается hand, editText cursor dont, кажется, обновление. Я считаю, что мне нужно сделать что-то в обратных вызовах textWatcher, но Я не знаю, что я должен делать.

Я отправляю свой код ниже.

public class AppHome extends AppBaseActivity {
    private EditText ed = null;
    private NumberFormat amountFormatter = null;
    private boolean  isUserInput = true;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.app_home_screen);

        ed = (EditText)findViewById(R.id.main_amount_textfield);
        amountFormatter = new DecimalFormat("##,##,###");


        ed.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                if(event.getAction() == KeyEvent.ACTION_DOWN) 
                    return true;
                String strippedAmount = ed.getText().toString().replace(",", "");
                if(keyCode == KeyEvent.KEYCODE_DEL){
                    //delete pressed, strip number of comas and then delete least significant digit.
                    strippedAmount = strippedAmount.substring(0, strippedAmount.length() - 1);
                    int amountNumeral = 0;
                    try{
                        amountNumeral = Integer.parseInt(strippedAmount);
                    } catch(NumberFormatException e){
                    }
                    myObject.amount = amountNumeral;
                    isUserInput = false;
                    setFormattedAmount(amountNumeral,ed.getId());
                }else if(keyCode == KeyEvent.KEYCODE_ENTER){
                    //enter pressed, save edits and resign keyboard
                    int amountNumeral = 0;
                    try{
                        amountNumeral = Integer.parseInt(strippedAmount);
                    } catch(NumberFormatException e){
                    }
                    myObject.amount = amountNumeral;
                    isUserInput = false;
                    setFormattedAmount(myObject.amount,ed.getId());
                    //save edits
                    save();
                    //resign keyboard..
                    InputMethodManager in = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    in.hideSoftInputFromWindow(AppHome.this.getCurrentFocus().getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);
                }
                return true;
            }
        });

        TextWatcher inputTextWatcher = new TextWatcher() {
            public void afterTextChanged(Editable s) { 
                if(isUserInput == false){
                    //textWatcher is recursive. When editText value is changed from code textWatcher callback gets called. So this variable acts as a flag which tells whether change is user generated or not..Possibly buggy code..:(
                    isUserInput = true;
                    return;
                }
                String strippedAmount = ed.getText().toString().replace(",", "");
                int amountNumeral = 0;
                try{
                    amountNumeral = Integer.parseInt(strippedAmount);
                } catch(NumberFormatException e){
                }
                isUserInput = false;
                setFormattedAmount(amountNumeral,ed.getId());
            }

            public void beforeTextChanged(CharSequence s, int start, int count, int after){
            }
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
        };

        ed.addTextChangedListener(inputTextWatcher);
    }//end of onCreate...

    public void setFormattedAmount(Integer amount, Integer inputBoxId){
        double amountValue = 0;
        String textString =null;
        TextView amountInputBox = (TextView) findViewById(inputBoxId);

        amountValue = Double.parseDouble(Integer.toString(amount));
        textString = amountFormatter.format(amountValue).toString();
        amountInputBox.setText(textString);
    }
}

Я знаю, что это большой вопрос, но я работаю над этой же проблемой в течение 2 дней. Я новичок в андроиде и все еще не могу поверить, что нет простого способа обрабатывать данные TextEdit на лету (я делал то же самое на iphone с легкостью). Спасибо всем

EDIT: после использования входного фильтра

InputFilter filter = new InputFilter() { 
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { 
            String strippedAmount = dest.toString() + source;
            strippedAmount = strippedAmount.replace(",", "");

        int amountNumeral = 0;
        try{
            amountNumeral = Integer.parseInt(strippedAmount);
        } catch(NumberFormatException e){
        }           
            return amountFormatter.format(amountNumeral).toString(); 
    } 
}; 

ed.setFilters(new InputFilter[]{filter}); 

Когда приложение запускается, я помещаю 1,234 в editText

myObject.amount = 1234;
ed.setText(amountFormatter.format(myObject.amount).toString());

Затем, когда пользователь нажимает на editText, появляется клавиатура и говорит, что пользователь вводит цифру 6

Я получаю: 61234 Я хочу: 12346

4b9b3361

Ответ 1

Хорошо, после большого удара головой, я нашел работу вокруг проблемы с позицией курсора. Я не знаю, правильно ли это, но я получил ее работу.

    TextWatcher inputTextWatcher = new TextWatcher() {
        public void afterTextChanged(Editable s) { 
            if(isUserInput == false){
                //textWatcher is recursive. When editText value is changed from code textWatcher callback gets called. So this variable acts as a flag which tells whether change is user generated or not..Possibly buggy code..:(
                isUserInput = true;
                ed.setSelection(ed.getText().length());
                return;
            }
            String strippedAmount = ed.getText().toString().replace(",", "");
            int amountNumeral = 0;
            try{
                amountNumeral = Integer.parseInt(strippedAmount);
            } catch(NumberFormatException e){
            }
            isUserInput = false;
            setFormattedAmount(amountNumeral,ed.getId());
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after){
        }
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }
    };
ed.addTextChangedListener(inputTextWatcher);


ed.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            int length          =   ed.getText().length();
            ed.setCursorVisible(true);
            ed.setSelection(length);
        }
    });

ed.setOnKeyListener(new View.OnKeyListener() {
    @Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
            if(event.getAction() == KeyEvent.ACTION_UP) 
                return true;
            String strippedAmount = ed.getText().toString().replace(",", "");
            if(keyCode == KeyEvent.KEYCODE_DEL){
                //delete pressed, strip number of comas and then delete least significant digit.
                strippedAmount = strippedAmount.substring(0, strippedAmount.length() - 1);
                int amountNumeral = 0;
                try{
                    amountNumeral = Integer.parseInt(strippedAmount);
                } catch(NumberFormatException e){
                }
                isUserInput = false;
                setFormattedAmount(amountNumeral,ed.getId());
                return true;
            }else if(keyCode == KeyEvent.KEYCODE_ENTER){
                //enter pressed, save edits and resign keyboard
                int amountNumeral = 0;
                try{
                    amountNumeral = Integer.parseInt(strippedAmount);
                } catch(NumberFormatException e){
                }
                isUserInput = false;
                setFormattedAmount(amountNumeral,ed.getId());
                //save edits
                //resign keyboard..
                InputMethodManager in = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                in.hideSoftInputFromWindow(AppHome.this.getCurrentFocus().getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);
                return true;
            }
            return false;
    }
});

Что я сделал, это onClick() из editText, я принудительно поместил курсор в конец текущего текста EditText, и я сделал то же самое, когда пользователь нажал любую цифру. Надеюсь, это поможет кому-то. Спасибо всем, кто пытался помочь.

Ответ 2

Для ввода Masked вы можете подклассы InputFilter

Ниже приведен пример подкласса InputFilter, который использует все буквы нижнего регистра:

   /**
     * This filter will capitalize all the lower case letters that are added
     * through edits.
     */
    public static class AllCaps implements InputFilter {
        public CharSequence filter(CharSequence source, int start, int end,
                                   Spanned dest, int dstart, int dend) {
            for (int i = start; i < end; i++) {
                if (Character.isLowerCase(source.charAt(i))) {
                    char[] v = new char[end - start];
                    TextUtils.getChars(source, start, end, v, 0);
                    String s = new String(v).toUpperCase();

                    if (source instanceof Spanned) {
                        SpannableString sp = new SpannableString(s);
                        TextUtils.copySpansFrom((Spanned) source,
                                                start, end, null, sp, 0);
                        return sp;
                    } else {
                        return s;
                    }
                }
            }

            return null; // keep original
        }
    }

Вышеприведенный код взят из Android-реализация InputFilter

Ответ 3

После нескольких часов работы я сделал маску ввода телефона. Для istance после ввода "123456" он преобразует его в "+1 (234) 56". После удаления любого символа из любой позиции курсор перемещается в нужное положение, а не в начало или конец.

В действии:

    etPhone.addTextChangedListener(new PhoneWatcher(etPhone));

В классе:

private class PhoneWatcher implements TextWatcher {
    private static final String PHONE_MASK = "+# (###) ###-##-##";
    private final char[] PHONE_MASK_ARRAY = PHONE_MASK.toCharArray();

    private boolean isInTextChanged;
    private boolean isInAfterTextChanged;
    private EditText editText;
    private int shiftCursor;
    private String text;
    private int cursor;

    public PhoneWatcher(EditText editText) {
        super();
        this.editText = editText;
        isInTextChanged = false;
        isInAfterTextChanged = false;
    }

    @Override
    public synchronized void beforeTextChanged(CharSequence s, int start, int count, int after) {
        shiftCursor = after - count;
    }

    @Override
    public synchronized void onTextChanged(CharSequence s, int start, int before, int count) {
        if (!isInTextChanged) {
            isInTextChanged = true;

            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < s.length(); i++) {
                char symbol = s.charAt(i);
                if (symbol >= '0' && symbol <= '9')
                    sb.append(symbol);
            }
            String digits = sb.toString();

            sb.setLength(0);
            int j = 0;
            for (int i = 0; i < digits.length(); i++) {
                char digit = digits.charAt(i);
                while (j < PHONE_MASK_ARRAY.length) {
                    if (PHONE_MASK_ARRAY[j] == '#') {
                        sb.append(digit);
                        j++;
                        break;
                    } else {
                        sb.append(PHONE_MASK_ARRAY[j]);
                        j++;
                    }
                }
            }

            cursor = editText.getSelectionStart();
            text = sb.toString();

            if (shiftCursor > 0) {
                if (cursor > text.length())
                    cursor = text.length();
                else {
                    while (cursor < PHONE_MASK_ARRAY.length && PHONE_MASK_ARRAY[cursor - 1] != '#') {
                        cursor++;
                    }
                }
            } else if (shiftCursor < 0) {
                while (cursor > 0 && PHONE_MASK_ARRAY[cursor - 1] != '#') {
                    cursor--;
                }
            }
        }
    }

    public synchronized void afterTextChanged(Editable s) {
        if (!isInAfterTextChanged) {
            isInAfterTextChanged = true;

            editText.setText(text);
            editText.setSelection(cursor);

            isInTextChanged = false;
            isInAfterTextChanged = false;
        }
    }
}