Як я можу змінити текст EditText, не запускаючи інструмент перегляду тексту?


104

У мене є EditTextполе з переглядачем тексту клієнта на ньому. У фрагменті коду мені потрібно змінити значення в EditText, яке я використовую .setText("whatever").

Проблема полягає в тому, що я змушую це змінити afterTextChanged метод виклику, який створив нескінченний цикл. Як я можу змінити текст без його запуску afterTextChanged?

Мені потрібен текст у методі afterTextChanged, тому не пропонуйте видаляти TextWatcher.

Відповіді:


70

Ви можете скасувати реєстрацію спостерігача, а потім перереєструвати його.

Крім того, ви можете встановити прапор так, щоб ваш спостерігач знав, коли ви щойно змінили текст (і тому повинні ігнорувати його).


Твого натяку мені достатньо. Насправді я реєстрував слухача в onResume, але не скасовував реєстрацію в onPause (), тому він дзвонив кілька разів.
Сміт

хтось може пояснити це? Технічно я новачок в android, мені потрібно більше деталей, спасибі.
Буді Мульо

116

Коротка відповідь

Ви можете перевірити, який перегляд у даний момент має фокус, щоб розрізняти події, викликані користувачем та програмою.

EditText myEditText = (EditText) findViewById(R.id.myEditText);

myEditText.addTextChangedListener(new TextWatcher() {

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        if (myEditText.hasFocus()) {
            // is only executed if the EditText was directly changed by the user
        }
    }

    //...
});

Довга відповідь

Як додаток до короткої відповіді: Якщо у вас myEditTextуже є фокус, коли ви програматично змінюєте текст, вам слід зателефонувати clearFocus(), то ви телефонуєте setText(...)і після того, як ви знову запросите фокус. Було б непогано включити це у функцію утиліти:

void updateText(EditText editText, String text) {
    boolean focussed = editText.hasFocus();
    if (focussed) {
        editText.clearFocus();
    }
    editText.setText(text);
    if (focussed) {
        editText.requestFocus();
    }
}

Для Котліна:

Оскільки Kotlin підтримує функції розширення, ваша утиліта може виглядати так:

fun EditText.updateText(text: String) {
    val focussed = hasFocus()
    if (focussed) {
        clearFocus()
    }
    setText(text)
    if (focussed) {
        requestFocus()
    }
}

у фрагменті getActivity().getCurrentFocus()та котлініactivity?.currentFocus
Мохаммед Реза Хахані

@MohammadRezaKhahani Ей! вам це не потрібно. Ви можете зателефонувати hasFocus()безпосередньо на EditText.
Віллі Менцель

Не ідеально. Що робити, якщо я хочу встановитиText () без тригера слухача, навіть якщо редакція має фокус?
Джая Пракаш

31
public class MyTextWatcher implements TextWatcher {
    private EditText et;

    // Pass the EditText instance to TextWatcher by constructor
    public MyTextWatcher(EditText et) {
        this.et = et;
    }

    @Override
    public void afterTextChanged(Editable s) {
        // Unregister self before update
        et.removeTextChangedListener(this);

        // The trick to update text smoothly.
        s.replace(0, s.length(), "text");

        // Re-register self after update
        et.addTextChangedListener(this);
    }
}

Використання:

et_text.addTextChangedListener(new MyTextWatcher(et_text));

Ви можете відчути невеликий відставання при швидкому введенні тексту, якщо ви використовуєте editText.setText () замість editable.replace () .


Чому це було знято? Це ідеальне рішення моєї проблеми, тоді як інші не спрацювали. Я додам, що для підключення підкласу TextWatcher не потрібно, щоб це рішення працювало. Спасибі Чан Чун Йому.
Майкл Фултон

Ваша ідея відреєстрації та перереєстрації TextWatcher чудово працює. Однак, хоча et.setText ("") працює, s.replace (0, s.length (), "") не відповідає.
stevehs17

@ stevehs17, я не знаю, як у вас код, але використання було оновлено дуже детально, спробуйте.
Чан Чун

15

Легкий трюк для виправлення ... поки ваша логіка для отримання нового значення редагування тексту є ідентичною (що це, мабуть, було б, але просто кажучи). У вашому методі прослуховування змінюйте текст редагування лише у тому випадку, якщо поточне значення відрізняється від останнього разу, коли ви змінили значення.

наприклад,

TextWatcher tw = new TextWatcher() {
  private String lastValue = "";

  @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) {

    // Return value of getNewValue() must only depend
    // on the input and not previous state
    String newValue = getNewValue(editText.getText().toString());
    if (!newValue.equals(lastValue)) {
      lastValue = newValue;

      editText.setText(newValue);
    }
  }
};

1
Це все одно призведе до того, що метод буде викликаний двічі, але ні?
Тревор Харт

Так, саме тому я заявив, що він повинен бути ідентичним (мав би зробити це більш зрозумілим) ... не ідеально, але ви повинні дійсно бути жорсткою оптимізацією, якщо ви переживаєте за накладні витрати одного дзвінка методу, який не працює. Якщо так, ви можете переорганізувати код, щоб видалити слухача перед його викликом setText()та повторно додати його після.
Джефрі Блатман

6

Я використовую такий спосіб:

mEditText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

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

            @Override
            public void afterTextChanged(Editable s) {
                if (mEditText.isFocused()) { //<-- check if is focused 
                    mEditText.setTag(true);
                }
            }
        });

І кожного разу, коли вам потрібно змінити текст програмно, спочатку очистіть фокус

mEditText.clearFocus();
mEditText.setText(lastAddress.complement);

5

Ви можете використовувати синтаксис DSL Kotlin, щоб мати загальне рішення для цього:

fun TextView.applyWithDisabledTextWatcher(textWatcher: TextWatcher, codeBlock: TextView.() -> Unit) {
    this.removeTextChangedListener(textWatcher)
    codeBlock()
    this.addTextChangedListener(textWatcher)
}

А всередині вашого TextWatcher ви можете використовувати його як:

editText.applyWithDisabledTextWatcher(this) {
    text = formField.name
}

Значно чистіше. Котлін DSL є приголомшливим
Anjal Saneen

4

Це добре працює для мене

EditText inputFileName; // = (EditText)findViewbyId(R.id...)
inputFileName.addTextChangedListener(new TextWatcher() {
        public void afterTextChanged(Editable s) {

            //unregistering for event in order to prevent infinity loop
            inputFileName.removeTextChangedListener(this);

            //changing input's text
            String regex = "[^a-z0-9A-Z\\s_\\-]";
            String fileName = s.toString();
            fileName = fileName.replaceAll(regex, "");
            s.replace(0, s.length(), fileName); //here is setting new text

            Log.d("tag", "----> FINAL FILE NAME: " + fileName);

            //registering back for text changes
            inputFileName.addTextChangedListener(this);
        }

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

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

3

Проблему можна легко вирішити за допомогою tagподаних файлів, і вам навіть не доведеться мати справу з фокусом editText.

Налаштування тексту та тегу програмно

editText.tag = "dummyTag"
editText.setText("whatever")
editText.tag = null

Перевірка на taginTextChanged

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
    if (editText.tag == null) {
       // your code
    }
}

3

Якщо вам потрібно зосередитися на EditTextзміні тексту, ви можете запитати фокус:

if (getCurrentFocus() == editText) {
    editText.clearFocus();
    editText.setText("...");
    editText.requestFocus();
}

1

спробуйте цю логіку: я хотів встановитиText (""), не переходячи до нескінченного циклу, і цей код працює для мене. Я сподіваюся, що ви можете змінити це, щоб відповідати вашим вимогам

        final EditText text= (EditText)findViewById(R.id.text);
        text.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }
        @Override
        public void afterTextChanged(Editable s) {
            if(s.toString().isEmpty())return;
            text.setText("");
            //your code
        }
    });

1

Ось зручний клас, який забезпечує простіший інтерфейс, ніж TextWatcher, для звичайного випадку, коли потрібно бачити зміни в міру їх виникнення. Це також дозволяє ігнорувати наступні зміни, як цього вимагала ОП.

public class EditTexts {
    public final static class EditTextChangeListener implements TextWatcher {
        private final Consumer<String> onEditTextChanged;
        private boolean ignoreNextChange = false;
        public EditTextChangeListener(Consumer<String> onEditTextChanged){
            this.onEditTextChanged = onEditTextChanged;
        }
        public void ignoreNextChange(){
            ignoreNextChange = true;
        }
        @Override public void beforeTextChanged(CharSequence __, int ___, int ____, int _____) { }
        @Override public void onTextChanged(CharSequence __, int ___, int ____, int _____) { }
        @Override public void afterTextChanged(Editable s) {
            if (ignoreNextChange){
                ignoreNextChange = false;
            } else {
                onEditTextChanged.accept(s.toString());
            }
        }
    }
}

Використовуйте його так:

EditTexts.EditTextChangeListener listener = new EditTexts.EditTextChangeListener(s -> doSomethingWithString(s));
editText.addTextChangedListener(listener);

Щоразу, коли ви хочете змінювати вміст, editTextне викликаючи каскаду рекурсивних редагувань, робіть це:

listener.ignoreNextChange();
editText.setText("whatever"); // this won't trigger the listener

0

Мій варіант:

public class CustomEditText extends AppCompatEditText{
    TextWatcher l;

    public CustomEditText(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public void setOnTextChangeListener(TextWatcher l) {
        try {
            removeTextChangedListener(this.l);
        } catch (Throwable e) {}
        addTextChangedListener(l);
        this.l = l;
    }

    public void setNewText(CharSequence s) {
        final TextWatcher l = this.l;
        setOnTextChangeListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

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

            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });
        setText(s);
        post(new Runnable() {
            @Override
            public void run() {
                setOnTextChangeListener(l);
            }
        });
    }


}

Встановити слухачів лише за допомогою setOnTextChangeListener () та встановити текст лише за допомогою setNewText (я хотів переосмислити setText (), але це остаточно)


0

Я створив абстрактний клас, який пом'якшує циклічну проблему, коли модифікація EditText здійснюється через TextWatcher.

/**
 * An extension of TextWatcher which stops further callbacks being called as a result of a change
 * happening within the callbacks themselves.
 */
public abstract class EditableTextWatcher implements TextWatcher {

    private boolean editing;

    @Override
    public final void beforeTextChanged(CharSequence s, int start, int count, int after) {
        if (editing)
            return;

        editing = true;
        try {
            beforeTextChange(s, start, count, after);
        } finally {
            editing = false;
        }
    }

    abstract void beforeTextChange(CharSequence s, int start, int count, int after);

    @Override
    public final void onTextChanged(CharSequence s, int start, int before, int count) {
    if (editing)
        return;

        editing = true;
        try {
            onTextChange(s, start, before, count);
        } finally {
            editing = false;
        }
    }

    abstract void onTextChange(CharSequence s, int start, int before, int count);

    @Override
    public final void afterTextChanged(Editable s) {
        if (editing)
            return;

        editing = true;
        try {
            afterTextChange(s);
        } finally {
            editing = false;
        }
    }    

    public boolean isEditing() {
        return editing;
    }

    abstract void afterTextChange(Editable s);
}

0

Дуже простий, встановити текст за допомогою цього методу

void updateText(EditText et, String text) {
   if (!et.getText().toString().equals(text))
       et.setText(text);
}

-2

Ви повинні переконатися, що ваше внесення змін до тексту є стабільним і не змінює текст, якщо не потрібні зміни. Зазвичай це був би будь-який вміст, який вже один раз переглядався.

Найпоширенішою помилкою є встановлення нового тексту в пов’язаному редакторі EditText або Editable, навіть якщо текст насправді не змінювався.

Крім того, якщо ви внесете зміни до редагованого замість якогось певного виду, ви можете легко використати повторно свій спостерігач, а також ви зможете перевірити його окремо за допомогою деяких тестових одиниць, щоб переконатися, що він має потрібний результат.

Оскільки Editable є інтерфейсом, ви навіть можете використовувати фіктивну його реалізацію, яка кидає RuntimeException, якщо викликається будь-який з його методів, які намагаються змінити його вміст, перевіряючи вміст, який повинен бути стабільним.


-2

Мій спосіб зробити це:

У сегменті запису

        EditText e_q;

        e_q = (EditText) parentView.findViewWithTag("Bla" + i);

        int id=e_q.getId();
        e_q.setId(-1);
        e_q.setText("abcd...");
        e_q.setId(id);

Слухач

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

        int id = view.getId();
        if(id==-1)return;

        ....

Працює все одно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.