70

我正在使用 EditText 控件过滤我的列表。我想在用户输入完 EditText 后 0.5 秒过滤列表。我为此目的使用了afterTextChanged事件。TextWatcher但是这个事件会随着 EditText 中每个字符的变化而上升。

我该怎么办?

4

17 回答 17

167

采用:

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更改文本时取消和重新安排。

有关设置延迟多长时间,请参阅这篇文章

于 2012-08-27T13:34:12.637 回答
52

更好地使用带有 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) {
    //
 }
于 2016-02-08T11:27:02.220 回答
16

您可以使用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
                }
            });
于 2017-09-16T14:24:24.613 回答
13

使用 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) {}
})}
于 2019-06-07T09:45:22.460 回答
11

以上解决方案都不适合我。

我需要一种方法让 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 并调用您应该对该文本执行的工作。

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

于 2017-04-08T11:20:52.347 回答
4

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

响应最新的编辑问题:如果您想在最后一次击键后等待特定时间,那么您必须在第一次按键时启动一个线程(使用 TextWatcher)。不断记录最新击键的时间。让线程休眠到最近一次击键的时间 + 0.5 秒。如果最新击键的时间戳尚未更新,请按照您的意图进行操作。

于 2012-08-27T12:36:20.527 回答
4

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

}
于 2019-02-27T08:59:42.923 回答
2

您还可以使用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
    }
});
于 2016-01-19T14:04:35.370 回答
2

您可以使用计时器。输入文本后,它将等待 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();
        }
    }
};
于 2019-07-16T09:11:53.130 回答
0

那是在输入完成时和之后的事件......添加一个 textWatcher 并在 onTextChanged 方法中放置:

if (charSequence.length() > 0){ 
    // Your code 
}
于 2018-09-27T18:38:16.940 回答
0

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

    }
});
于 2018-12-06T11:50:27.370 回答
0

试试这个

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

}

于 2020-02-14T14:24:50.643 回答
0

如果你用 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) {

    }
}
于 2021-01-15T00:35:49.283 回答
0

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

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()
    }
}
于 2021-02-08T20:15:34.583 回答
0

最好的方法是移动光标

setSelection(it.toString().length)

使用这种形式不要使用树或协程来睡眠 N tine

于 2021-03-17T12:53:58.567 回答
-1

您可以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;
    }
});
于 2012-08-27T12:36:33.150 回答
-1

为您的案例使用计时器并不是最佳解决方案,因为每次都会创建一个新对象。根据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
        }
    }
);
于 2016-02-02T08:17:27.410 回答