Как я могу что-то сделать, через 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
                }
            });

http://reactivex.io/documentation/operators/debounce.html

Ни одно из вышеуказанных решений не помогло мне.

Мне нужен был способ, чтобы 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
        }
    }
);
Другие вопросы по тегам