web-dev-qa-db-fra.com

Comment puis-je faire quelque chose, 0,5 seconde après le changement de texte dans mon EditText?

Je filtre ma liste en utilisant un EditText. Je veux filtrer la liste 0,5 seconde après que l'utilisateur a fini de taper dans EditText . J'ai utilisé l'événement afterTextChanged de TextWatcher à cette fin. Mais cet événement augmente à chaque changement de personnage dans EditText.

Que devrais-je faire?

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

L'astuce consiste à annuler et à réorganiser Timer à chaque fois, lorsque le texte dans EditText est modifié. Bonne chance!

UPDATE Pour ceux qui sont intéressés par la durée du délai, voir cet article .

124
Berťák

Mieux utiliser Handler avec la méthode postDelayed (). Dans l'implémentation d'Android, Timer créera un nouveau fil à chaque fois pour exécuter la tâche. Handler a cependant son propre Looper qui peut être attaché à n'importe quel thread que nous souhaitons, nous ne paierons donc pas de coût supplémentaire pour créer un thread.

Exemple 

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

Vous pouvez utiliser RxBindings , c'est la meilleure solution. Reportez-vous au guide sur l'opérateur anti-rebond de RxJava, je suis sûr que cela fera très bien dans votre cas.

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

10
Anton Makhrov

Aucune des solutions ci-dessus n'a fonctionné pour moi.

J'avais besoin d'un moyen pour TextWatcher de ne pas déclencher tous les caractères que je saisis dans ma vue de recherche et de ne pas afficher de progrès, ce qui signifie que je dois accéder à un fil d'interface utilisateur.

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

Suppression du gestionnaire sur chaque onTextChanged (appelé lorsque l'utilisateur saisit un nouveau caractère). afterTextChanged est appelé après que le texte a été modifié dans le champ de saisie où nous pouvons commencer un nouveau Runnable, mais l'annulera si l'utilisateur tape plus de caractères (Pour plus d'informations, lorsque ces rappels sont appelés, voir ceci ). Si l'utilisateur ne saisit plus de caractères, intervalle passera dans postDelayed et le travail que vous devez effectuer avec ce texte sera appelé.

Ce code ne sera exécuté qu'une fois par intervalle, pas pour toutes les entrées utilisateur clés. J'espère que cela aidera quelqu'un dans le futur. 

8
Beemo

Comment déterminez-vous qu'ils ont fini d'écrire? Que l'edittext perd le focus? Ensuite, il y a setOnFocusChangedListener .

Réponse à la dernière modification à la question: Si vous souhaitez attendre un certain temps après le dernier appui sur une touche, vous devez démarrer un fil lors de la première pression sur une touche (utilisez TextWatcher). Enregistrez constamment l'heure du dernier coup de touche. Laissez le fil s'endormir à l'heure de la dernière frappe + 0,5 seconde. Si l'horodatage de la dernière frappe n'a pas été mis à jour, faites ce que vous aviez prévu. 

3
pgsandstrom

Avec les fonctions d'extension et les routines de 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) {}
})}

Vous pouvez également utiliser l'interface TextWatcher et créer votre classe personnalisée qui l'implémente pour réutiliser plusieurs fois votre CustomTextWatcher. Vous pouvez également transmettre des vues ou tout ce dont vous pourriez avoir besoin à son constructeur:

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

}

Maintenant, dans votre activité, vous pouvez l'utiliser comme ceci:

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

Vous pouvez utiliser timer, après avoir tapé le texte, il attendra 600 ms. Placez le code dans afterTextChanged () en utilisant un délai de 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();
        }
    }
};
1

Dans la langue Kotlin, vous pouvez faire comme ça

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

    }
0
Hardik Lakhani

Si vous voulez ignorer textWatcher pour la première fois, ajoutez le code suivant: Cela permettra à textWatcher d’apporter des modifications à partir de la deuxième fois.

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

                }
            });
0
priyam