在EditText中更改文本后0.5秒,我该怎么办?

时间:2012-08-27 12:31:28

标签: android filter android-edittext textwatcher textchanged

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

我该怎么办?

17 个答案:

答案 0 :(得分:130)

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

答案 1 :(得分:38)

最好使用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) {
    //
 }

答案 2 :(得分:10)

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

http://reactivex.io/documentation/operators/debounce.html

答案 3 :(得分:8)

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

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

此代码每个时间间隔只运行一次,而不是每个关键用户输入。希望它能帮助将来的某个人。

答案 4 :(得分:3)

你如何确定他们已完成写作? edittext失去了焦点?然后有setOnFocusChangedListener

回复最新的编辑内容:如果您想在最后一次按键后等待一段特定时间,则必须在第一次按键时启动一个线程(使用TextWatcher)。不断记录最新击键时间。让线程睡到最新击键时间+ 0.5秒。如果最新击键的时间戳尚未更新,请执行您的预期操作。

答案 5 :(得分:1)

您还可以使用TextWatcher接口创建实现它的自定义类,以便多次重复使用CustomTextWatcher,并且还可以将视图或其他任何内容传递给其构造函数:

public abstract class CustomTextWatcherimplements 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
        }
    });

答案 6 :(得分:0)

那是在键入结束时和结束后发生的事件……添加一个textWatcher,并在onTextChanged方法中放入:

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

答案 7 :(得分:0)

最好的方法是移动光标

<块引用>

setSelection(it.toString().length)

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

答案 8 :(得分: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()
    }
}

答案 9 :(得分:0)

如果你用 Kotlin 编写,你可以这样做。

这种方法使用协程代替线程(如果你通过 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) {

    }
}

答案 10 :(得分: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()
}

}

答案 11 :(得分:0)

您可以使用计时器,在键入文字后它将等待600毫秒。通过使用600毫秒的延迟将代码放入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();
        }
    }
};

答案 12 :(得分:0)

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

答案 13 :(得分:0)

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

    }

答案 14 :(得分: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) {

                }
            });

答案 15 :(得分:-1)

对于您的案例使用计时器不是最佳解决方案,因为每次都会创建新对象。根据Timer文档(http://developer.android.com/reference/java/util/Timer.html),最好使用ScheduledThreadPoolExecutor -

  

&#34;计时器安排一次性或重复执行任务。比较喜欢   ScheduledThreadPoolExecutor用于新代码。&#34;

这是更好的方法

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

答案 16 :(得分:-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;
    }
});