How can I do something, 0.5 second after text changed in my EditText?

核能气质少年 提交于 2019-12-02 15:22:14
Berťák
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
            );
        }
    }
);

The trick is in canceling and re-scheduling Timer each time, when text in EditText gets changed. Good luck!

UPDATE For those interested in how long to set the delay, see this post.

Better use Handler with postDelayed() method. In the android's implementation Timer will create new thread each time to run the task. Handler however has its own Looper that can be attached to whatever thread we wish, so we won't pay extra cost to create thread.

Example

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

You can use RxBindings, it's the best solution. See guide to RxJava operator debounce, I'm sure that will do great in your case.

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

Beemo

Non of the above solution worked for me.

I needed a way for TextWatcher to not fire on every character I input inside my search view and show some progress, meaning I need to access UI thread.

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

Removing Handler on every onTextChanged (which is called when the user inputs a new character). afterTextChanged is called after the text has been changed inside input field where we can start new Runnable, but will cancel it if user types more characters (For more info, when these callback are called, see this). If user doesn't input anymore characters, interval will pass in postDelayed and it will call work you should do with that text.

This code will run only once per interval, not for every key user inputs. Hope it helps someone in the future.

How do you determine that they have finished writing? That the edittext loses focus? Then there is setOnFocusChangedListener.

Responding to latest edit in question: If you want to wait a specific time after the latest key stroke, then you have to start up a thread at the first keypress (use TextWatcher). Constantly register the time of the latest key stroke. Let the thread sleep to the the time of the latest keystroke + 0.5 seconds. If the timestamp of the latest keystroke has not been updated, do whatever you had intended.

With Kotlin extension functions and coroutines:

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

You can also use TextWatcher interface and create your custom class that implements it to re-use many times your CustomTextWatcher and also you can pass views or whatever you might need to its constructor:

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

}

Now in your activity you can use it like this:

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

In Kotlin Language you can do like this

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

    }

You can use timer, after typing the text it will wait for 600 ms. Put the code inside afterTextChanged() by using delay of 600 ms.

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

that is the event while and after finish of typing ... add a textWatcher and in the onTextChanged method put :

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

If you want to skip textWatcher for the first time only then add following code: This will allow textWatcher make any change from the second time.

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

                }
            });
Amit Jayant

You can use EditorActionListener for that purpose.

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

Using timer for your case is not the best solution because of creating new object everytime. According to Timer documentation(http://developer.android.com/reference/java/util/Timer.html) it's better to use ScheduledThreadPoolExecutor -

"Timers schedule one-shot or recurring tasks for execution. Prefer ScheduledThreadPoolExecutor for new code."

Here is better approach

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
        }
    }
);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!