如何在 EditText 控件中的文本更改后0.5秒执行某些操作?

我正在使用 EditText 控件筛选列表。我想过滤列表 用户输入完 EditText 后0.5秒。为此,我使用了 TextWatcherafterTextChanged事件。但是,对于 EditText 中的每个字符更改,此事件都会上升。

我该怎么办?

57060 次浏览

你如何确定他们已经完成了写作? 编辑文本失去焦点? 然后是 SetOnFocusChangedListener

响应有问题的最新编辑 : 如果您想在最新的击键之后等待一个特定的时间,那么您必须在第一次按键时启动一个线程(使用 TextWatcher)。不断注册最新按键的时间。让线程休眠到最近一次击键的时间 + 0.5秒。如果最近一次击键的时间戳没有更新,那么执行您希望的操作。

您可以为此目的使用 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;
}
});

用途:

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 (e.g., manipulating views).
}
},
DELAY
);
}
}
);

诀窍在于,当 EditText中的文本发生变化时,每次都要取消和重新安排 Timer

有关设置延迟的时间,请参见 这篇文章

您还可以使用 TextWatcher接口并创建自定义类,该类实现了多次重用您的 CustomTextWatcher,您还可以向其构造函数传递视图或您可能需要的任何内容:

public abstract class CustomTextWatcher implements 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 the abstract method to leave the
// implementation to the implementing class


}

现在在你的活动中,你可以这样使用它:

// Notice I'm passing in the constructor of CustomTextWatcher
// myEditText I needed to use
myEditText.addTextChangedListener(new CustomTextWatcher(myEditText) {
@Override
public void textWasChanged() {
//doSomething(); This is method inside your activity
}
});

为您的情况使用计时器不是最好的解决方案,因为每次都会创建一个新对象。根据 定时器文档,最好使用 ScheduledThreadPoolExecator-

”计时器为执行安排一次性或重复性任务 新代码的计划线程池执行器

这里有一个更好的方法

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

最好使用带 postDelayed ()方法的 管理员。在 Android 的实现中,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) {
//
}

以上的方法对我都不管用。

我需要一种方法让 TextWatcher 不会触发我在搜索视图中输入的每个字符并显示一些进展,这意味着我需要访问 UI 线程。

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 上的 Handler (当用户输入新字符时调用)。After TextChanged 是在输入字段中的文本被更改之后调用的,我们可以在这个字段中启动一个新的 Runnable,但是如果用户键入更多的字符(更多信息,当这些回调被调用时,看这个) ,它将取消它。如果用户不再输入任何字符,间隔将传入 postDelayed,它将调用您应该对该文本进行的工作。

此代码每个间隔只运行一次,而不是针对每个键用户输入。

您可以使用 RxBindings; 这是最好的解决方案。请参阅 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 并在 onTextChanged 方法中放入:

if (charSequence.length() > 0){
// Your code
}

如果您只想跳过 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) {


}
});

在科特林语言中,你可以这样做:

tv_search.addTextChangedListener(mTextWatcher)


private val mTextWatcher: TextWatcher = object : TextWatcher {
private var timer = Timer()
private val DELAY: Long = 1000L


override fun afterTextChanged(s: Editable?) {
timer.cancel()
timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {


// Do your stuff here
}
}, DELAY)
}


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


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


}

具有 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) {}
})}

你可以用计时器。在输入文本之后,它将等待600毫秒。通过使用600毫秒的延迟,将代码放入 After TextChanged ()中。

@Override
public void afterTextChanged(Editable arg0) {
// The 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); // 600 ms 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) {
// The user is typing: reset already started timer (if existing)
if (timer != null) {
timer.cancel();
}
}
};

试试这个

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

}

如果你在 Kotlin 写作,你可以这样做。

这种方法使用协同程序代替 Thread (如果您通过 Timer ()执行的话)。此外,您可以通过 launchWhenCreated 等来控制 debounceJob的生命周期。

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


}
}

观察文本更改事件的另一种方法是使用协同程序通道。

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

最好的方法是移动光标

SetSelection (it.toString () . length)

用这种形式不要使用树或协同睡眠 N 个时间