Android EditText - événement de saisie terminé

122

Je veux attraper un événement lorsque l'utilisateur a fini de modifier EditText.

Comment ceci peut être fait?

Eddyuk
la source
1
@PadmaKumar La manière la plus naturelle serait de perdre l'attention sur EditText - alors l'utilisateur a définitivement terminé, mais par expérience, il semble que tous les widgets Android ne captent pas correctement la mise au point (j'ai eu ces problèmes avec les Spinners et les boutons) - pour que les autres widgets ne perdent pas de vue ...
AgentKnopf
3
pourquoi une si grande plate-forme nous permet de gérer ce genre de fonctions simples? google ne perdra rien s'il ajoute au moins ces fonctionnalités principales à ses bibliothèques de support
MBH
Si vous connaissez la longueur exacte de la chaîne que l'utilisateur va entrer, récupérez le texte dans afterTextChanged. Ex:override fun afterTextChanged(s: Editable?) { if (s.toString().length == 5) { val enteredString = s.toString() }
Shylendra Madda
2
Je me gratte la tête en me demandant pourquoi c'est si difficile sous Android que nous (moi y compris) devons rechercher et démarrer une discussion en ligne et de nombreuses solutions différentes avec plus de 10 lignes de code ..?
nsandersen

Réponses:

118

Lorsque l'utilisateur a terminé l'édition, il / elle appuiera sur DoneouEnter

((EditText)findViewById(R.id.youredittext)).setOnEditorActionListener(
    new EditText.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
            if (actionId == EditorInfo.IME_ACTION_SEARCH ||
                    actionId == EditorInfo.IME_ACTION_DONE ||
                    event != null &&
                    event.getAction() == KeyEvent.ACTION_DOWN &&
                    event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
                if (event == null || !event.isShiftPressed()) {
                   // the user is done typing. 

                   return true; // consume.
                }                
            }
            return false; // pass on to other listeners. 
        }
    }
);
Reno
la source
50
Peux-tu en être sûr? J'ai l'habitude de taper dans / sur le champ modifiable suivant - je n'appuie pratiquement jamais sur Entrée / Terminé - et ce que j'ai vu de nos clients, eux non plus ... Je parle d'une liste d'Edittexts / Comboboxes, etc. Si vous ne donnez à l'utilisateur qu'un seul champ de saisie et le forcez à appuyer sur un bouton avant de pouvoir passer à d'autres champs, alors c'est évidemment une autre histoire ...
AgentKnopf
5
N'oubliez pas que vous devez également avoir android: singleLine = "true" défini pour votre texte d'édition. Merci à stackoverflow.com/questions/15901863/…
Steven Smith
1
Bonne solution mais peut planter avec un pointeur nul ... 'event' peut être nul sur l'action Done donc event.isShiftPressed () se bloque. Si vous utilisez cela, ajoutez des vérifications de pointeur nul.
Georgie
Modification ajoutée pour vérifier la nullité "Événement".
Word Rearranger
3
et si vous appuyez sur le bouton RETOUR qui ferme le clavier?
user347187
183

Mieux, vous pouvez également utiliser l'écouteur EditText onFocusChange pour vérifier si l'utilisateur a terminé la modification: (Ne comptez pas sur l'utilisateur appuyant sur le bouton Terminé ou Entrée du clavier logiciel)

 ((EditText)findViewById(R.id.youredittext)).setOnFocusChangeListener(new OnFocusChangeListener() {

    @Override
    public void onFocusChange(View v, boolean hasFocus) {

      // When focus is lost check that the text field has valid values.

      if (!hasFocus) { {
         // Validate youredittext
      }
    }
 });

Remarque: pour plus d'un EditText, vous pouvez également laisser votre classe implémenter View.OnFocusChangeListenerpuis définir les écouteurs sur chacun de vous EditText et les valider comme ci-dessous

((EditText)findViewById(R.id.edittext1)).setOnFocusChangeListener(this);
((EditText)findViewById(R.id.edittext2)).setOnFocusChangeListener(this);

    @Override
    public void onFocusChange(View v, boolean hasFocus) {

      // When focus is lost check that the text field has valid values.

      if (!hasFocus) {
        switch (view.getId()) {
           case R.id.edittext1:
                 // Validate EditText1
                 break;
           case R.id.edittext2:
                 // Validate EditText2
                 break;
        }
      }
    }
Vinayak Bevinakatti
la source
60
Cela ne fonctionne pas s'il n'y a qu'un seul focusable EditText. Il ne perdra jamais sa concentration.
Bart Friederichs
Quelle est la meilleure façon si nous n'en avons qu'un seul EditText? Dois-je utiliser onEditorAction?
Tux
3
S'il n'y en a qu'un EditText, vous validez ce que fait l'utilisateur pour quitter l'écran.
Christine
J'ai essayé cela mais cela n'a pas fonctionné. Chaque fois que j'appuyais sur Entrée, cela me donnait un message similaire à celui-ci: W/ViewRootImpl: Cancelling event due to no window focus: MotionEvent { action=ACTION_CANCEL, actionButton=0, id[0]=0, x[0]=605.52246, y[0]=969.4336, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=238238, downTime=235422, deviceId=0, source=0x1002 }même si j'ai tapé quelque chose. C'est un edittext qui n'accepte que les nombres.
ahitt6345
1
Il y a un côté de l'utilisation de l'écouteur OnFocusChange: il ne sera pas appelé si EditText a le focus et que le périphérique est tourné! Je l'ai résolu en mettant someOtherView.requestFocus () dans Activities onPause (). (avant super.onPause ()) De cette façon, il est sûr que EditText perd le focus et que l'auditeur soit appelé.
allofmex
60

Personnellement, je préfère la soumission automatique après la fin de la saisie. Voici comment vous pouvez détecter cet événement.

Déclarations et initialisation:

private Timer timer = new Timer();
private final long DELAY = 1000; // in ms

Écouteur dans par exemple onCreate ()

EditText editTextStop = (EditText) findViewById(R.id.editTextStopId);
    editTextStop.addTextChangedListener(new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count,
                int after) {
        }
        @Override
        public void onTextChanged(final CharSequence s, int start, int before,
                int count) {
            if(timer != null)
                timer.cancel();
        }
        @Override
        public void afterTextChanged(final Editable s) {
            //avoid triggering event when text is too short
            if (s.length() >= 3) {              

                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
                        serviceConnector.getStopPoints(s.toString());
                    }

                }, DELAY);
            }
        }
    });

Ainsi, lorsque le texte est modifié, le minuteur commence à attendre les prochains changements. Quand ils se produisent, la minuterie est annulée puis redémarrée.

ZimaXXX
la source
11
c'est génial, mais sachez que les minuteries peuvent provoquer des fuites de mémoire, alors utilisez-les à la Handlerplace
Moh Mah
C'est super, Thansk.
VipPunkJoshers Droopy
qu'est-ce que serviceConnector?
altruiste
@altruistic C'est juste un fragment de mon code dont j'ai extrait la réponse. C'est l'endroit où la logique devrait aller.
ZimaXXX
21

Vous pouvez le faire en utilisant setOnKeyListener ou en utilisant un textWatcher comme:

Définir l'observateur de texte editText.addTextChangedListener(textWatcher);

puis appelle

private TextWatcher textWatcher = new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            //after text changed
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count,
                int after) {
        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    };
ShineDown
la source
27
J'ai bien peur que ce ne soit pas la solution (complète) - le textWatcher se déclenche après chaque modification - si vous tapez bonjour, il se déclenchera pour h, e, l, l et o - il ne sait pas vraiment quand l'utilisateur a "terminé" . Ce serait formidable si TextWatcher savait réellement quand le widget a perdu le focus et que l'utilisateur est passé ...
AgentKnopf
4

Bien que de nombreuses réponses pointent dans la bonne direction, je pense qu'aucune d'elles ne répond à ce à quoi pensait l'auteur de la question. Ou du moins j'ai compris la question différemment parce que je cherchais une réponse à un problème similaire. Le problème est "Comment savoir quand l'utilisateur arrête de taper sans qu'il appuie sur un bouton" et déclencher une action (par exemple l'auto-complétion). Si vous voulez faire cela, démarrez le minuteur dans onTextChanged avec un délai que vous considérez que l'utilisateur a arrêté de taper (par exemple 500-700ms), pour chaque nouvelle lettre lorsque vous démarrez le minuteur, annulez la précédente (ou au moins utilisez une sorte de signalent que lorsqu'ils cochent, ils ne font rien). Voici un code similaire à celui que j'ai utilisé:

new Timer().schedule(new TimerTask() {
  @Override
  public void run() {
     if (!running) {                            
        new DoPost().execute(s.toString());
  });
 }
}, 700);

Notez que je modifie le drapeau booléen en cours d'exécution dans ma tâche asynchrone (la tâche obtient le json du serveur pour l'auto-complétion).

Gardez également à l'esprit que cela crée de nombreuses tâches de minuterie (je pense qu'elles sont planifiées sur le même fil de discussion que vous, mais vous devriez vérifier cela), il y a donc probablement de nombreux endroits à améliorer, mais cette approche fonctionne également et l'essentiel est que vous devriez utiliser un minuteur car il n'y a pas d'événement "L'utilisateur a arrêté de taper"

Igor Čordaš
la source
Pouvez-vous s'il vous plaît donner plus d'informations et un exemple de code à ce sujet?
John Ernest Guadalupe
L'essentiel du code est dans la réponse, pour un exemple plus détaillé, veuillez consulter la réponse de ZimaXXX sur cette même page qui utilise la même approche. Je ne sais pas quelles autres informations je pourrais ajouter, si vous pouvez spécifier ce qui n'est pas clair, j'essaierai d'ajouter des détails.
Igor Čordaš
4

@Reno et @Vinayak B répondent ensemble si vous souhaitez masquer le clavier après l'action

textView.setOnEditorActionListener(new EditText.OnEditorActionListener() {
    @Override
    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
        if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE) {
            InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(textView.getWindowToken(), 0);
            return true;
        }
        return false;
    }
});

textView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (!hasFocus) {
             // your action here
        }
    }
});
Tyler Davis
la source
3

Une approche différente ... voici un exemple: si l'utilisateur a un délai de 600 à 1000 ms lors de la frappe, vous pouvez considérer qu'il est arrêté.

 myEditText.addTextChangedListener(new TextWatcher() {
             
            private String s;
            private long after;
			private Thread t;
            private Runnable runnable_EditTextWatcher = new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        if ((System.currentTimeMillis() - after) > 600)
                        {
                            Log.d("Debug_EditTEXT_watcher", "(System.currentTimeMillis()-after)>600 ->  " + (System.currentTimeMillis() - after) + " > " + s);
                            // Do your stuff
                            t = null;
                            break;
                        }
                    }
                }
            };
            
            @Override
            public void onTextChanged(CharSequence ss, int start, int before, int count) {
                s = ss.toString();
            }
            
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }
            
            @Override
            public void afterTextChanged(Editable ss) {
                after = System.currentTimeMillis();
                if (t == null)
                {
                    t = new Thread(runnable_EditTextWatcher);
                      t.start();
                }
            }
        });

Cristi Maris
la source
2

D'accord, cela fonctionnera à 100% à coup sûr.

Vous devrez d'abord configurer l'écouteur si le clavier est affiché ou masqué. Si le clavier est affiché, l'utilisateur est probablement en train de taper, sinon il est terminé.

final View activityRootView = findViewById(android.R.id.content);
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {

                    Rect r = new Rect();
                    //r will be populated with the coordinates of your view that area still visible.
                    activityRootView.getWindowVisibleDisplayFrame(r);

                    int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
                    if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...

                        isTyping = true;
                    } else {
//to make sure this will call only once when keyboard is hide.
                        if(isTyping){
                            isTyping = false;
                        }
                    }
            }
        });
Krit
la source
1
qu'en est-il de plusieurs modifications de texte? Et le heightDiff > 100truc est effrayant à mon humble avis.
Bondax
Pas de problème, ça marche bien. Jetez un œil à ce stackoverflow.com/questions/2150078/…
Krit
2

J'ai résolu ce problème de cette façon. J'ai utilisé du kotlin.

        var timer = Timer()
        var DELAY:Long = 2000

        editText.addTextChangedListener(object : TextWatcher {

            override fun afterTextChanged(s: Editable?) {
                Log.e("TAG","timer start")
                timer = Timer()
                timer.schedule(object : TimerTask() {
                    override fun run() {
                        //do something
                    }
                }, DELAY)
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
                Log.e("TAG","timer cancel ")
                timer.cancel() //Terminates this timer,discarding any currently scheduled tasks.
                timer.purge() //Removes all cancelled tasks from this timer's task queue.
            }
        })
Fahed Hermoza
la source
0

J'ai eu le même problème et je ne voulais pas compter sur l'utilisateur appuyant sur Terminé ou Entrée.

Ma première tentative a été d'utiliser l'écouteur onFocusChange, mais il est arrivé que mon EditText ait le focus par défaut. Lorsque l'utilisateur appuyait sur une autre vue, onFocusChange était déclenché sans que l'utilisateur lui ait jamais attribué le focus.

La solution suivante l'a fait pour moi, où le onFocusChange est attaché si l'utilisateur a touché le EditText:

final myEditText = new EditText(myContext); //make final to refer in onTouch
myEditText.setOnTouchListener(new OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            myEditText.setOnFocusChangeListener(new OnFocusChangeListener() {

                @Override
                public void onFocusChange(View v, boolean hasFocus) {
                    if(!hasFocus){
                        // user is done editing
                    }
                }
            }
        }
}

Dans mon cas, lorsque l'utilisateur a terminé de modifier l'écran a été rendu à nouveau, renouvelant ainsi l'objet myEditText. Si le même objet est conservé, vous devez probablement supprimer l'écouteur onFocusChange dans onFocusChange pour éviter le problème onFocusChange décrit au début de cet article.

Jos
la source
Vous définissez un écouteur de changement de focus à chaque fois qu'un événement tactile se produit, ce qui sera plusieurs fois par seconde. Ne faites pas cela.
jsonfry
0

J'ai eu le même problème en essayant d'implémenter la saisie instantanée sur l'application de chat. essayez d'étendre EditText comme suit:

public class TypingEditText extends EditText implements TextWatcher {

private static final int TypingInterval = 2000;


public interface OnTypingChanged {
    public void onTyping(EditText view, boolean isTyping);
}
private OnTypingChanged t;
private Handler handler;
{
    handler = new Handler();
}
public TypingEditText(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    this.addTextChangedListener(this);
}

public TypingEditText(Context context, AttributeSet attrs) {
    super(context, attrs);
    this.addTextChangedListener(this);
}

public TypingEditText(Context context) {
    super(context);
    this.addTextChangedListener(this);
}

public void setOnTypingChanged(OnTypingChanged t) {
    this.t = t;
}

@Override
public void afterTextChanged(Editable s) {
    if(t != null){
        t.onTyping(this, true);
        handler.removeCallbacks(notifier);
        handler.postDelayed(notifier, TypingInterval);
    }

}

private Runnable notifier = new Runnable() {

    @Override
    public void run() {
        if(t != null)
            t.onTyping(TypingEditText.this, false);
    }
};

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }


@Override
public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { }

}

Nadav
la source
0

Je l'ai terminée avec le même problème et je ne pouvais pas utiliser la solution avec onEditorAction ou onFocusChange et je ne voulais pas essayer la minuterie. Une minuterie est trop dangereuse pour peut goûter, à cause de tous les threads et trop imprévisible, car vous ne savez pas quand votre code est exécuté.

OnEditorAction n'attrape pas lorsque l'utilisateur quitte sans utiliser de bouton et si vous l'utilisez, notez que KeyEvent peut être nul. Le focus n'est pas fiable aux deux extrémités, l'utilisateur peut obtenir le focus et quitter sans entrer de texte ou sélectionner le champ et l'utilisateur n'a pas besoin de quitter le dernier champ EditText.

Ma solution utilise onFocusChange et un indicateur défini lorsque l'utilisateur commence à éditer du texte et une fonction pour obtenir le texte de la dernière vue focalisée, que j'appelle en cas de besoin.

Je viens d'effacer le focus sur tous mes champs de texte pour tromper le code de vue de texte de congé, le code clearFocus n'est exécuté que si le champ a le focus. J'appelle la fonction dans onSaveInstanceState afin de ne pas avoir à enregistrer l'indicateur (mEditing) comme état de la vue EditText et lorsque des boutons importants sont cliqués et lorsque l'activité est fermée.

Soyez prudent avec TexWatcher car il est souvent appelé J'utilise la condition sur le focus pour ne pas réagir lorsque le code onRestoreInstanceState entre du texte. je

final EditText mEditTextView = (EditText) getView();

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

        }

        @Override
        public void afterTextChanged(Editable s) {
            if (!mEditing && mEditTextView.hasFocus()) {
                mEditing = true;
            }
        }
    });
    mEditTextView.setOnFocusChangeListener(new View.OnFocusChangeListener() {

        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (!hasFocus && mEditing) {
                mEditing = false;
                ///Do the thing
            }
        }
    });
protected void saveLastOpenField(){
    for (EditText view:getFields()){
            view.clearFocus();
    }
}
Rezder
la source
0

J'ai fait quelque chose comme cette classe abstraite qui peut être utilisée à la place du type TextView.OnEditorActionListener.

abstract class OnTextEndEditingListener : TextView.OnEditorActionListener {

    override fun onEditorAction(textView: TextView?, actionId: Int, event: KeyEvent?): Boolean {

        if(actionId == EditorInfo.IME_ACTION_SEARCH ||
                actionId == EditorInfo.IME_ACTION_DONE ||
                actionId == EditorInfo.IME_ACTION_NEXT ||
                event != null &&
                event.action == KeyEvent.ACTION_DOWN &&
                event.keyCode == KeyEvent.KEYCODE_ENTER) {

            if(event == null || !event.isShiftPressed) {
                // the user is done typing.
                return onTextEndEditing(textView, actionId, event)
            }
        }
        return false // pass on to other listeners
    }

    abstract fun onTextEndEditing(textView: TextView?, actionId: Int, event: KeyEvent?) : Boolean
}
Michał Ziobro
la source
0

Simple à déclencher, terminer la saisie dans EditText

a fonctionné pour moi, si vous utilisez java, convertissez-le

À Kotlin

youredittext.doAfterTextChanged { searchTerm ->
val currentTextLength = searchTerm?.length
    Handler().postDelayed({
        if (currentTextLength == searchTerm?.length) {
            // your code 
           Log.d("aftertextchange", "ON FINISH TRIGGER")
          }
       }, 3000)
}
Kharis Azhar
la source