我正在使用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
}
},
DELAY
);
}
}
);
当Timer
中的文本发生变化时,诀窍在于每次取消和重新安排EditText
。祝好运!
更新对于有兴趣设置延迟时间的人,请参阅this post。
如果您只想第一次跳过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语言中你可以这样做
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) {
}
}
您可以使用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文档(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
}
}
);
最好使用Handler和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) {
//
}
你可以使用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
}
});
上述解决方案都不适合我。
我需要一种方法让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,但如果用户输入更多字符,将取消它(有关更多信息,当调用这些回调时,see this)。如果用户不再输入字符,则间隔将在postDelayed中传递,并且它将调用您应该对该文本执行的工作。
此代码每个间隔仅运行一次,而不是每个关键用户输入。希望它能帮助将来的某个人。
你如何确定他们已经写完了? edittext失去了焦点?然后有setOnFocusChangedListener。
回答最新的编辑问题:如果你想在最后一次击键后等待一段特定的时间,那么你必须在第一次按键时启动一个线程(使用TextWatcher)。不断记录最新击键时间。让线程睡到最新击键时间+ 0.5秒。如果最新击键的时间戳尚未更新,请执行您的预期操作。
您还可以使用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 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
}
});
使用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 ms的延迟将代码放在afterTextChanged()内。
@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();
}
}
};
这是在输入完成时和之后的事件...添加一个textWatcher并在onTextChanged方法中放入:
if (charSequence.length() > 0){// your code }