我正在使用 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() 方法一起使用。在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) {
//
}
;这是最好的解决方案。请参阅 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
}
});
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 上删除处理程序(当用户输入新字符时调用)。 afterTextChanged 在输入字段内的文本更改后被调用,我们可以在其中启动一个新的 Runnable,但如果用户输入更多字符,它将取消它(有关更多信息,当调用这些回调时,参见 this
)。如果用户不再输入任何字符,间隔将在 postDelayed 中传递,它将调用您应该对该文本执行的工作。 此代码每个时间间隔仅运行一次,而不是针对每个关键用户输入。
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) {
}
}
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()
}
}
接口并创建实现它的自定义类,以多次重用您的 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
}
});
@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()
}
}
if (charSequence.length() > 0){
// Your code
}
这将允许 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) {
}
});
此方法使用协程而不是线程(如果您将通过 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 = [email protected]
.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) {
}
}
setSelection(it.toString().length)
使用这种形式,不要使用树或协程来睡眠N tine
searchProductsViewModel.viewModelScope.launch {
delay(500)
searchProductsViewModel.products()
}
,最好使用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
}
}
);
来实现此目的。
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;
}
});