Как я могу изменить текст EditText, не вызывая средство просмотра текста?

#java #android #android-edittext #textwatcher #android-textwatcher

#java #Android #обработка событий #бесконечный цикл

Вопрос:

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

Проблема в том, что как только я делаю это изменение afterTextChanged , вызывается метод, который создает бесконечный цикл. Как я могу изменить текст, не вызывая afterTextChanged?

Мне нужен текст в методе afterTextChanged, поэтому не предлагайте удалять TextWatcher .

Ответ №1:

Короткий ответ

Вы можете проверить, какое представление в данный момент находится в фокусе, чтобы различать события, вызванные пользователем и программой.

 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:

Поскольку Kotlin поддерживает функции расширения, ваша служебная функция может выглядеть следующим образом:

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

Комментарии:

1. Не идеально. Что делать, если я хочу использовать setText() без прослушивателя триггера, даже если edittext имеет фокус?

Ответ №2:

Вы можете отменить регистрацию средства просмотра, а затем повторно зарегистрировать его.

Кроме того, вы можете установить флаг, чтобы ваш наблюдатель знал, когда вы только что изменили текст самостоятельно (и, следовательно, должны игнорировать его).

Ответ №3:

Java:

 public class MyTextWatcher implements TextWatcher {
    private EditText editText;

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

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

        // The trick to update text smoothly.
        e.replace(0, e.length(), e.toString());

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

    ...
}
 

Kotlin:

 class MyTextWatcher(private val editText: EditText) : TextWatcher {

  override fun afterTextChanged(e: Editable) {
    editText.removeTextChangedListener(this)
    e.replace(0, e.length, e.toString())
    editText.addTextChangedListener(this)
  }

  ...
}
 

Использование:

 et_text.addTextChangedListener(new MyTextWatcher(et_text));
 

При быстром вводе текста может возникнуть небольшая задержка, если вы используете EditText.setText() вместо editable.replace() .

Комментарии:

1. Почему это было отклонено? Это идеальное решение моей проблемы, тогда как другие не работали. Я добавлю, что для работы этого решения нет необходимости создавать подкласс TextWatcher. Спасибо Чан Чун Ему.

2. Ваша идея отмены регистрации и повторной регистрации TextWatcher отлично работает. Однако, хотя et.setText(«») работает, s.replace(0, s.length(), «») не работает.

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

Ответ №4:

Простой трюк , который можно исправить … пока ваша логика для получения нового значения edit text является идемпотентной (что, вероятно, было бы, но просто говорю). В вашем методе прослушивания изменяйте текст редактирования только в том случае, если текущее значение отличается от последнего изменения значения.

например ,,

 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. Однако это все равно приведет к тому, что метод будет вызываться дважды, нет?

2. Да, именно поэтому я заявил, что он должен быть идемпотентным (должен был сделать это более понятным)… не идеально, но вы должны действительно жестко оптимизировать, если вас беспокоят накладные расходы на один вызов метода, который не работает. Если это так, вы могли бы реорганизовать код, чтобы удалить прослушиватель перед его вызовом setText() и повторно добавить его после.

Ответ №5:

Вы можете использовать синтаксис Kotlin DSL, чтобы иметь общее решение для этого:

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

И внутри вашего TextWatcher вы можете использовать его как:

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

Комментарии:

1. Намного чище. Kotlin DSL потрясающий

2. Это оно. Спасибо.

Ответ №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);
 

Ответ №7:

Это хорошо работает для меня

 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) { }
    });
 

Ответ №8:

Проблема может быть легко решена с помощью tag filed, и вам даже не придется иметь дело с фокусом EditText.

Установка текста и тега программно

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

Проверка tag в onTextChanged

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

Комментарии:

1. Это хороший способ! большое вам спасибо!

Ответ №9:

Если вам нужно сосредоточиться на EditText изменении текста, вы можете запросить фокус:

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

Ответ №10:

Это просто, просто сделайте это так

 editText.addTextChangedListener(object : TextWatcher {

        private var isEditing = false

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {


        }

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {

        }

        override fun afterTextChanged(s: Editable?) {
            if(!isEditing){
                isEditing = true
                editText.setText("Hello World!")
                isEditing = false
            }

        }
    })
 

таким образом, он не хранится в бесконечном цикле

Ответ №11:

попробуйте эту логику: я хотел установить текст («»), не переходя к бесконечному циклу, и этот код работает для меня. Я надеюсь, что вы можете изменить это в соответствии с вашими требованиями

         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
        }
    });
 

Ответ №12:

Вот удобный класс, который предоставляет более простой интерфейс, чем TextWatcher, для обычного случая, когда требуется видеть изменения по мере их возникновения. Это также позволяет игнорировать следующее изменение по запросу OP.

 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
 

Ответ №13:

Мой вариант:

 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(), но это окончательно)

Ответ №14:

Я создал абстрактный класс, который устраняет циклическую проблему, возникающую при внесении изменений в 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);
}
 

Ответ №15:

Очень просто, установите текст с помощью этого метода

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

Ответ №16:

Мое решение для этого очень похоже на другие, только я настраиваю его с помощью viewbindings

Я создал следующий TextWatcher

 class ControlledTextWatcher(
    private val parent: TextView,
    private val onChange: ((text: CharSequence?, start: Int, before: Int, count: Int) -> Unit)?,
    private val beforeChange: ((text: CharSequence?, start: Int, count: Int, after: Int) -> Unit)? = null,
    private val afterChange: ((editable: Editable?) -> Unit)? = null
) : TextWatcher {
    init {
        parent.addTextChangedListener(this)
    }

    private var enabled = true

    var text: String?
        get() = parent.value
        set(value) {
            this.enabled = false
            parent.text = value
            this.enabled = true
        }

    var res: Int
        get() = throw RuntimeException("String resource cannot be retrieved after being set")
        set(value) {
            parent.text = parent.context.getString(value)
        }


    override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
        if (enabled)
            beforeChange?.invoke(s, start, count, after)
    }

    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
        if (enabled)
            onChange?.invoke(s, start, before, count)
    }

    override fun afterTextChanged(s: Editable?) {
        if (enabled)
            afterChange?.invoke(s)
    }

    fun detach() {
        parent.removeTextChangedListener(this)
    }
}
 

и я использую его в основном с привязками представления следующим образом

класс TestActivity : AppCompatActivity() {

 class TestActivity : AppCompatActivity() {
    private lateinit var binding: ActivityTestBinding
    private val edit by lazy { ControlledTextWatcher(binding.text, this::textChanged }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityTestBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
 

поэтому, когда я хочу внести изменения в фактический EditText Я использую атрибут text or res ControlledTextWatcher например, так:

 edit.text = "hello world" //this does not trigger the text watcher
 

но когда пользователь изменит EditText это, это сработает

к сожалению, с этим решением, если вы хотите изменить другие параметры EditText , вам нужно либо получить оригинал EditText через привязки, либо скопировать эти функции в ControlledTextWatcher

также вы должны быть осторожны при внесении изменений, afterChange потому что изменение публикуется в TextView , поэтому вы можете получить бесконечный цикл

Ответ №17:

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

Наиболее распространенной ошибкой является установка нового текста в связанный редактируемый текст или в Редактируемый, даже если текст на самом деле не был изменен.

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

Поскольку Editable — это интерфейс, вы можете даже использовать его фиктивную реализацию, которая выдает исключение RuntimeException, если вызывается какой-либо из его методов, которые пытаются изменить его содержимое, при тестировании содержимого, которое должно быть стабильным.

Ответ №18:

Мой способ сделать это:

В сегменте записи

         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;

        ....
 

Работает в любом случае.