#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;
....
Работает в любом случае.