Как я могу что-то сделать, через 0,5 секунды после изменения текста в моем EditText?
Я фильтрую свой список, используя EditText. Я хочу отфильтровать список через 0,5 секунды после того, как пользователь закончил печатать в EditText. Я использовал afterTextChanged
событие TextWatcher
для этого. Но это событие возникает при каждом изменении символа в EditText.
Что я должен делать?
17 ответов
editText.addTextChangedListener(
new TextWatcher() {
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
private Timer timer=new Timer();
private final long DELAY = 1000; // milliseconds
@Override
public void afterTextChanged(final Editable s) {
timer.cancel();
timer = new Timer();
timer.schedule(
new TimerTask() {
@Override
public void run() {
// TODO: do what you need here (refresh list)
// you will probably need to use runOnUiThread(Runnable action) for some specific actions
}
},
DELAY
);
}
}
);
Хитрость в отмене и перепланировании Timer
каждый раз, когда текст в EditText
меняется Удачи!
ОБНОВЛЕНИЕ Для тех, кто заинтересован в том, как долго установить задержку, см. Этот пост.
Лучше использовать Handler с методом postDelayed(). В реализации андроида Timer будет каждый раз создавать новый поток для запуска задачи. У обработчика, однако, есть свой собственный Looper, который можно присоединить к любому потоку, который мы хотим, поэтому мы не будем платить дополнительные расходы на создание потока.
пример
Handler handler = new Handler(Looper.getMainLooper() /*UI thread*/);
Runnable workRunnable;
@Override public void afterTextChanged(Editable s) {
handler.removeCallbacks(workRunnable);
workRunnable = () -> doSmth(s.toString());
handler.postDelayed(workRunnable, 500 /*delay*/);
}
private final void doSmth(String str) {
//
}
С функциями расширения Kotlin и сопрограммами:
fun AppCompatEditText.afterTextChangedDebounce(delayMillis: Long, input: (String) -> Unit) {
var lastInput = ""
var debounceJob: Job? = null
val uiScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(editable: Editable?) {
if (editable != null) {
val newtInput = editable.toString()
debounceJob?.cancel()
if (lastInput != newtInput) {
lastInput = newtInput
debounceJob = uiScope.launch {
delay(delayMillis)
if (lastInput == newtInput) {
input(newtInput)
}
}
}
}
}
override fun beforeTextChanged(cs: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(cs: CharSequence?, start: Int, before: Int, count: Int) {}
})}
Вы можете использовать RxBindings, это лучшее решение. Смотрите руководство по debounce оператора RxJava, я уверен, что это будет хорошо в вашем случае.
RxTextView.textChanges(editTextVariableName)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe(new Action1<String>() {
@Override
public void call(String value) {
// do some work with the updated text
}
});
Ни одно из вышеуказанных решений не помогло мне.
Мне нужен был способ, чтобы TextWatcher не запускал каждый символ, который я вводил в окне поиска, и показывал некоторый прогресс, то есть мне нужен доступ к потоку пользовательского интерфейса.
private final TextWatcher textWatcherSearchListener = new TextWatcher() {
final android.os.Handler handler = new android.os.Handler();
Runnable runnable;
public void onTextChanged(final CharSequence s, int start, final int before, int count) {
handler.removeCallbacks(runnable);
}
@Override
public void afterTextChanged(final Editable s) {
//show some progress, because you can access UI here
runnable = new Runnable() {
@Override
public void run() {
//do some work with s.toString()
}
};
handler.postDelayed(runnable, 500);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
};
Удаление обработчика на каждый onTextChanged (который вызывается, когда пользователь вводит новый символ). afterTextChanged вызывается после того, как текст был изменен внутри поля ввода, где мы можем запустить новый Runnable, но отменит его, если пользователь введет больше символов (для получения дополнительной информации, когда эти обратные вызовы вызываются, см. это). Если пользователь больше не вводит символы, интервал пройдет в postDelayed, и это вызовет работу, которую вы должны сделать с этим текстом.
Этот код будет выполняться только один раз за интервал, а не для каждого ключевого пользовательского ввода. Надеюсь, это поможет кому-то в будущем.
В Kotlin Language вы можете сделать это
tv_search.addTextChangedListener(mTextWatcher)
private val mTextWatcher: TextWatcher = object : TextWatcher {
private var timer = Timer()
private val DELAY: Long = 1000
override fun afterTextChanged(s: Editable?) {
timer.cancel();
timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
//DO YOUR STUFF HERE
}
}, 1000)
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
}
Другой способ наблюдать за событием изменения текста - использовать Coroutines Channels.
lifecycleScope.launchWhenCreated {
editText.afterTextChanged {
// do something
}
}
Создайте функцию расширения для сбора данных из потока
suspend fun EditText.afterTextChanged(afterTextChanged: suspend (String) -> Unit) {
val watcher = Watcher()
this.addTextChangedListener(watcher)
watcher.asFlow()
.debounce(500)
.collect { afterTextChanged(it) }
}
Создайте класс Watcher, чтобы предлагать текст после изменения
class Watcher : TextWatcher {
private val channel = ConflatedBroadcastChannel<String>()
override fun afterTextChanged(editable: Editable?) {
channel.offer(editable.toString())
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
fun asFlow(): Flow<String> {
return channel.asFlow()
}
}
Как вы определяете, что они закончили писать? Что текст редактирования теряет фокус? Тогда есть setOnFocusChangedListener.
Отвечая на последнее редактируемое вопрос: если вы хотите подождать определенное время после последнего нажатия клавиши, то вам нужно запустить поток при первом нажатии клавиши (используйте TextWatcher). Постоянно регистрируйте время последнего нажатия клавиши. Пусть поток спит до времени последнего нажатия клавиши + 0,5 секунды. Если отметка времени последнего нажатия клавиши не была обновлена, делайте все, что вы хотели.
Вы также можете использовать интерфейс TextWatcher и создать свой собственный класс, который реализует его для многократного повторного использования вашего CustomTextWatcher, а также вы можете передавать представления или все, что вам может понадобиться, в его конструктор:
public abstract class CustomTextWatcherimplements TextWatcher { //Notice abstract class so we leave abstract method textWasChanged() for implementing class to define it
private final TextView myTextView; //Remember EditText is a TextView so this works for EditText also
public AddressTextWatcher(TextView tView) { //Notice I'm passing a view at the constructor, but you can pass other variables or whatever you need
myTextView= tView;
}
private Timer timer = new Timer();
private final int DELAY = 500; //milliseconds of delay for timer
@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(final Editable s) {
timer.cancel();
timer = new Timer();
timer.schedule(
new TimerTask() {
@Override
public void run() {
textWasChanged();
}
},
DELAY
);
}
public abstract void textWasChanged(); //Notice abstract method to leave implementation to implementing class
}
Теперь в своей деятельности вы можете использовать это так:
myEditText.addTextChangedListener(new CustomTextWatcher(myEditText) { //Notice I'm passing in constructor of CustomTextWatcher myEditText I needed to use
@Override
public void textWasChanged() {
//doSomething(); this is method inside your activity
}
});
Вы можете использовать таймер, после ввода текста он будет ждать 600 мс. Поместите код внутри afterTextChanged(), используя задержку в 600 мс.
@Override
public void afterTextChanged(Editable arg0) {
// user typed: start the timer
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// do your actual work here
editText.setText(et.getText().toString());
}
}, 600); // 600ms delay before the timer executes the „run“ method from TimerTask
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// nothing to do here
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// user is typing: reset already started timer (if existing)
if (timer != null) {
timer.cancel();
}
}
};
лучший способ - переместить курсор
setSelection(it.toString(). длина)
с этой формой не используйте дерево или сопрограмму для сна N tine
Если вы хотите пропустить textWatcher только в первый раз, добавьте следующий код: Это позволит textWatcher вносить любые изменения со второго раза.
Boolean firstchange=false;
profileEmailEditText.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) {
if (firstchange) {
emailAlertText.setVisibility(View.VISIBLE);
}
else {
firstchange=true;
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
Если вы пишете на Kotlin, вы можете сделать это так.
Этот подход использует сопрограммы вместо Thread (если вы будете делать это через Timer()). Более того, вы можете контролировать жизненный цикл
debounceJob
с launchWhenCreated и т. д.
private val onNumberListener = object : TextWatcher {
private var debounceJob: Job? = null
private val DELAY: Long = 1000L
override fun afterTextChanged(s: Editable?) {
debounceJob?.cancel()
debounceJob = this@FragmentName.viewLifecycleOwner.lifecycle.coroutineScope
.launch(Dispatchers.Main) {
delay(DELAY)
viewModel.onNumberChange(s?.toString() ?: "")
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
}
Это событие во время и после окончания ввода... добавьте textWatcher и в метод onTextChanged поместите:
if (charSequence.length() > 0){// your code }
Попробуй это
class DelayTextWatcher(val ms: Long = 500, val textChanged: (String) -> Unit) : TextWatcher {
private var timer: CountDownTimer? = null
override fun afterTextChanged(p0: Editable) {
timer?.cancel()
timer = object : CountDownTimer(ms, ms) {
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
textChanged(p0.toString())
}
}.start()
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
fun dispose() {
timer?.cancel()
}
}
Ты можешь использовать EditorActionListener
для этой цели.
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
//Do something here
return true;
}
return false;
}
});
Использование таймера для вашего случая не лучшее решение, потому что каждый раз создается новый объект. Согласно документации по таймеру ( http://developer.android.com/reference/java/util/Timer.html) лучше использовать ScheduledThreadPoolExecutor -
"Таймеры планируют выполнение одноразовых или повторяющихся задач. Предпочитайте ScheduledThreadPoolExecutor для нового кода".
Вот лучше подход
Runnable runnabledelayedTask = new Runnable(){
@Override
public void run(){
//TODO perform any operation here
}
};
editText.addTextChangedListener(
new TextWatcher() {
@Override public void onTextChanged(CharSequence s, int start, int before, int count) { }
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
private final long DELAY = 500; // milliseconds
@Override
public void afterTextChanged(final Editable s) {
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(1);
ScheduledFuture sf = scheduledPool.schedule(callabledelayedTask, DELAY, TimeUnit.MILLISECONDS);
//you can cancel ScheduledFuture when needed
}
}
);