我正在使用 EditText 控件过滤我的列表。我想在用户输入完 EditText 后 0.5 秒过滤列表。我为此目的使用了afterTextChanged
事件。TextWatcher
但是这个事件会随着 EditText 中每个字符的变化而上升。
我该怎么办?
我正在使用 EditText 控件过滤我的列表。我想在用户输入完 EditText 后 0.5 秒过滤列表。我为此目的使用了afterTextChanged
事件。TextWatcher
但是这个事件会随着 EditText 中每个字符的变化而上升。
我该怎么办?
采用:
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
);
}
}
);
诀窍是在Timer
每次EditText
更改文本时取消和重新安排。
有关设置延迟多长时间,请参阅这篇文章。
更好地使用带有 postDelayed() 方法的Handler 。在Android的实现中,Timer每次都会创建一个新的线程来运行任务。然而Handler有它自己的 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) {
//
}
您可以使用RxBindings;这是最好的解决方案。请参阅 RxJava 运算符 debounce指南。我相信这对你的情况会很好。
RxTextView.textChanges(editTextVariableName)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe(new Action1<String>() {
@Override
public void call(String value) {
// Do some work with the updated text
}
});
使用 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) {}
})}
以上解决方案都不适合我。
我需要一种方法让 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(当用户输入新字符时调用)。afterTextChanged 在输入字段内的文本发生更改后调用,我们可以在其中启动一个新的 Runnable,但如果用户键入更多字符,它将取消它(有关更多信息,当调用这些回调时,请参阅this)。如果用户不再输入任何字符,则间隔将传入 postDelayed 并调用您应该对该文本执行的工作。
此代码每个时间间隔仅运行一次,而不是针对每个关键用户输入。
你如何确定他们已经完成了写作?编辑文本失去焦点?然后是setOnFocusChangedListener。
响应最新的编辑问题:如果您想在最后一次击键后等待特定时间,那么您必须在第一次按键时启动一个线程(使用 TextWatcher)。不断记录最新击键的时间。让线程休眠到最近一次击键的时间 + 0.5 秒。如果最新击键的时间戳尚未更新,请按照您的意图进行操作。
在 Kotlin 语言中,你可以这样做:
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) {
}
}
您还可以使用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
}
});
您可以使用计时器。输入文本后,它将等待 600 毫秒。使用 600 毫秒的延迟将代码放入 afterTextChanged() 中。
@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();
}
}
};
那是在输入完成时和之后的事件......添加一个 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) {
}
});
试试这个
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 编写,你可以这样做。
这种方法使用协程而不是线程(如果您将通过 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) {
}
}
观察文本更改事件的另一种方法是使用协程通道。
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 tine
您可以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;
}
});
为您的案例使用计时器并不是最佳解决方案,因为每次都会创建一个新对象。根据Timer 文档,最好使用 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
}
}
);