Comment masquer le clavier logiciel sur Android après avoir cliqué en dehors de EditText?

354

Ok, tout le monde sait que pour masquer un clavier, vous devez implémenter:

InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);

Mais le gros problème ici est de savoir comment masquer le clavier lorsque l'utilisateur touche ou sélectionne un autre endroit qui n'est pas un EditTextou le softKeyboard?

J'ai essayé d'utiliser le onTouchEvent()sur mon parent, Activitymais cela ne fonctionne que si l'utilisateur touche en dehors de toute autre vue et qu'il n'y a pas de défilement.

J'ai essayé d'implémenter un écouteur tactile, clic, focus sans succès.

J'ai même essayé d'implémenter ma propre vue de défilement pour intercepter les événements tactiles, mais je ne peux obtenir que les coordonnées de l'événement et non la vue cliquée.

Existe-t-il un moyen standard de le faire ?? dans l'iPhone, c'était vraiment facile.

htafoya
la source
Eh bien, j'ai réalisé que le scrollview n'était pas vraiment le problème, mais les étiquettes qui sont là. La vue est une disposition verticale avec quelque chose comme: TextView, EditText, TextView, EditText, etc .. et les textViews ne laissera pas le edittext de se concentrer lâche et cacher le clavier
htafoya
Vous pouvez trouver une solution getFields()ici: stackoverflow.com/questions/7790487/…
Reto
Le clavier peut être fermé en appuyant sur le bouton de retour, donc je dirais qu'il est douteux que cela en vaille la peine
gerrytan
4
J'ai trouvé cette réponse: stackoverflow.com/a/28939113/2610855 La meilleure.
Loenix

Réponses:

575

L'extrait suivant masque simplement le clavier:

public static void hideSoftKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = 
        (InputMethodManager) activity.getSystemService(
            Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(
        activity.getCurrentFocus().getWindowToken(), 0);
}

Vous pouvez mettre cela dans une classe utilitaire, ou si vous le définissez dans une activité, évitez le paramètre d'activité ou appelez hideSoftKeyboard(this).

La partie la plus délicate est de savoir quand l'appeler. Vous pouvez écrire une méthode qui réitère chaque étape Viewde votre activité, et vérifier si c'est un instanceof EditTextsi ce n'est pas enregistrer un setOnTouchListenerdans ce composant et tout se mettra en place. Si vous vous demandez comment faire, c'est en fait assez simple. Voici ce que vous faites, vous écrivez une méthode récursive comme la suivante, en fait, vous pouvez l'utiliser pour faire n'importe quoi, comme configurer des polices de caractères personnalisées, etc. Voici la méthode

public void setupUI(View view) {

    // Set up touch listener for non-text box views to hide keyboard.
    if (!(view instanceof EditText)) {
        view.setOnTouchListener(new OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                hideSoftKeyboard(MyActivity.this);
                return false;
            }
        });
    }

    //If a layout container, iterate over children and seed recursion.
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            View innerView = ((ViewGroup) view).getChildAt(i);
            setupUI(innerView);
        }
    }
}

C'est tout, appelez simplement cette méthode après vous setContentViewdans votre activité. Dans le cas où vous vous demandez quel paramètre vous passeriez, c'est celui iddu conteneur parent. Attribuez un idà votre conteneur parent comme

<RelativeLayoutPanel android:id="@+id/parent"> ... </RelativeLayout>

et appeler setupUI(findViewById(R.id.parent)), c'est tout.

Si vous souhaitez l'utiliser efficacement, vous pouvez créer une extension Activityet insérer cette méthode, et faire en sorte que toutes les autres activités de votre application étendent cette activité et l'appellent setupUI()dans la onCreate()méthode.

J'espère que cela aide.

Si vous utilisez plus d'une activité, définissez l'ID commun à la disposition parent comme <RelativeLayout android:id="@+id/main_parent"> ... </RelativeLayout>

Ensuite, étendez une classe à partir de Activityet définissez setupUI(findViewById(R.id.main_parent))Dans son OnResume()et étendez cette classe au lieu de `` Activitéin your program


Voici une version Kotlin de la fonction ci-dessus:

@file:JvmName("KeyboardUtils")

fun Activity.hideSoftKeyboard() {
    currentFocus?.let {
        val inputMethodManager = ContextCompat.getSystemService(this, InputMethodManager::class.java)!!
        inputMethodManager.hideSoftInputFromWindow(it.windowToken, 0)
    }
}
Navneeth G
la source
Je ne me suis pas testé mais il semble que cela fonctionnerait et comme il a des critiques élevées, je changerai la réponse acceptée.
htafoya
4
Ça ne devrait pas être très dur? Je n'ai plus de programmation Android pour le moment, alors corrigez-moi si je me trompe. Vous pouvez en quelque sorte suivre le EditText ciblé à tout moment et lui demander de perdre son focus lors d'un OnTouchEvent?
Navneeth G
25
Je ne sais pas si quelqu'un d'autre a rencontré ce problème, mais cela provoque le blocage de l'application lorsque vous appelez hideSoftKeyboard si rien n'est ciblé. Vous pouvez résoudre ce problème en entourant la deuxième ligne de la méthode avecif(activity.getCurrentFocus() != null) {...}
Frank Cangialosi
14
Le problème avec cette approche est qu'elle suppose que toutes les autres vues n'auront jamais besoin de définir un OnTouchListenerpour elles. Vous pouvez simplement définir cette logique dans une ViewGroup.onInterceptTouchEvent(MotionEvent)vue racine.
Alex.F
2
Ne fonctionne pas lorsque je clique sur d'autres contrôles en gardant le clavier ouvert
mohitum
285

Vous pouvez y parvenir en procédant comme suit:

  1. Rendre la vue parent (vue de contenu de votre activité) cliquable et focalisable en ajoutant les attributs suivants

        android:clickable="true" 
        android:focusableInTouchMode="true" 
  2. Implémenter une méthode hideKeyboard ()

        public void hideKeyboard(View view) {
            InputMethodManager inputMethodManager =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
  3. Enfin, définissez le onFocusChangeListener de votre edittext.

        edittext.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    hideKeyboard(v);
                }
            }
        });

Comme indiqué dans l'un des commentaires ci-dessous, cela peut ne pas fonctionner si la vue parent est un ScrollView. Dans ce cas, le ClickTable et le focusableInTouchMode peuvent être ajoutés sur la vue directement sous ScrollView.

vida
la source
67
À mon avis, c'est la bonne réponse. Moins de code, pas d'itérations inutiles ...
gattshjoty
14
J'aime beaucoup cette réponse. Une chose à noter est que cela n'a pas fonctionné pour moi lors de l'ajout clickableet focusableInTouchModede mon ScrollViewélément racine . J'ai dû ajouter au parent direct de mon EditTextqui était un LinearLayout.
Adam Johns
10
A parfaitement fonctionné pour moi. Cependant, si vous avez deux widgets edittext, vous devez vous assurer que vous gérez correctement la mise au point sur les deux, sinon vous basculerez inutilement le clavier.
Marka A
1
@MarkaA Je n'ai eu aucun problème avec cela. Lorsque je clique sur l'autre EditText, le clavier reste, si on clique sur l'arrière-plan, il se cache comme il se doit. J'ai eu une autre manipulation sur onFocusChange, changeant juste l'arrière-plan du EditText quand il a le focus, rien de compliqué.
CularBytes
3
@Shrikant - Je voyais aussi le scintillement avec plusieurs textes d'édition. Je viens de définir onFocusChangeListener sur le parent plutôt que sur chaque texte d'édition, et j'ai changé la condition pour dire si (hasFocus) {hideKeyboard (v); } Je n'ai plus remarqué de scintillement en changeant de texte d'édition btwn.
manisha
69

Remplacez simplement le code ci-dessous dans Activity

 @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
    return super.dispatchTouchEvent(ev);
}
sumit sonawane
la source
4
Solution simple, ajoutez de l'activité et il s'occupera également du fragment
Rohit Maurya
1
Une autre solution consiste à créer BaseActivity et à l'étendre à toutes les activités
sumit sonawane
1
Solution brillante
Ahmed Adel Ismail
1
vous m'avez sauvé du clignotement de mon écran lors de la fermeture du clavier. Pouces vers le haut!
Dimas Mendes
1
Sympa, mais c'était trop beau pour être vrai: simple, très court, et ça a fonctionné ... hélas, il y a un problème: lorsque le clavier est affiché, à chaque fois qu'on touche le EditText qui a demandé au clavier, il descend et automatiquement.
Chrysotribax
60

Je trouve la réponse acceptée un peu compliquée.

Voici ma solution. Ajoutez un OnTouchListenerà votre mise en page principale, c'est-à-dire:

findViewById(R.id.mainLayout).setOnTouchListener(this)

et mettez le code suivant dans la méthode onTouch.

InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);

De cette façon, vous n'avez pas à parcourir toutes les vues.

roepit
la source
@roepit - je reçois une exception classCastexception pour essayer de caster une mise en page dans une vue. est-ce que je manque quelque chose?
katzenhut
Pouvez-vous référencer votre code quelque part? Je ne peux pas dire ce qui ne va pas quand je ne peux pas regarder votre mise en page et votre code d'activité / fragment et cetera.
roepit le
Meilleure réponse là-bas, essayant toujours de comprendre comment cela fonctionne.
user40797
cela fonctionnera-t-il si vous cliquez sur la barre de titre de l'application?
user1506104
1
Cela fonctionne parfaitement pour cacher le clavier! Notez que cela ne défocalise pas réellement le EditText, il masque simplement le clavier. Pour débloquer également le EditText, ajoutez par exemple android:onClick="stealFocusFromEditTexts"au xml de la vue parent puis public void stealFocusFromEditTexts(View view) {}à son activité. La méthode au clic n'a rien à faire, elle doit simplement exister pour que la vue parent soit focalisable / sélectionnable, ce qui est nécessaire pour voler le focus à l'enfant EditText
Jacob R
40

J'ai une autre solution pour masquer le clavier:

InputMethodManager imm = (InputMethodManager) getSystemService(
    Activity.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);

Ici, passez HIDE_IMPLICIT_ONLYà la position showFlaget 0à la position de hiddenFlag. Il fermera de force le clavier virtuel.

Saurabh Pareek
la source
3
Merci, c'est du travail ..... surtout j'ai essayé mais ça ne fonctionne pas pendant que j'obtiens de la valeur de la boîte de dialogue editext text nd closoing dialogbox ...
PankajAndroid
Merci, cela ne fonctionne que, et c'est beaucoup plus propre que l'autre ci-dessus! +1
Edmond Tamas
Cela fonctionne comme prévu, merci pour votre solution +1
tryp
Fonctionne comme un charme pour moi. +1 pour une solution élégante.
saintjab
2
désolé, mais cette méthode est à bascule, donc si l'état du clavier est déjà fermé, il affichera le clavier
HendraWD
16

Eh bien, je parviens à résoudre quelque peu le problème, j'ai outrepassé le dispatchTouchEvent sur mon activité, là j'utilise ce qui suit pour masquer le clavier.

 /**
 * Called to process touch screen events. 
 */
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

    switch (ev.getAction()){
        case MotionEvent.ACTION_DOWN:
            touchDownTime = SystemClock.elapsedRealtime();
            break;

        case MotionEvent.ACTION_UP:
            //to avoid drag events
            if (SystemClock.elapsedRealtime() - touchDownTime <= 150){  

                EditText[] textFields = this.getFields();
                if(textFields != null && textFields.length > 0){

                    boolean clickIsOutsideEditTexts = true;

                    for(EditText field : textFields){
                        if(isPointInsideView(ev.getRawX(), ev.getRawY(), field)){
                            clickIsOutsideEditTexts = false;
                            break;
                        }
                    }

                    if(clickIsOutsideEditTexts){
                        this.hideSoftKeyboard();
                    }               
                } else {
                    this.hideSoftKeyboard();
                }
            }
            break;
    }

    return super.dispatchTouchEvent(ev);
}

ÉDITER: La méthode getFields () est juste une méthode qui retourne un tableau avec les champs de texte dans la vue. Pour éviter de créer ce tableau à chaque contact, j'ai créé un tableau statique appelé sFields, qui est renvoyé par la méthode getFields (). Ce tableau est initialisé sur les méthodes onStart () telles que:

sFields = new EditText[] {mUserField, mPasswordField};


Ce n'est pas parfait, le temps de l'événement glisser n'est basé que sur l'heuristique, donc parfois il ne se cache pas lors de l'exécution de longues clics, et j'ai également fini par créer une méthode pour obtenir tous les editTexts par vue; sinon le clavier se cacherait et s'afficherait en cliquant sur un autre EditText.

Pourtant, des solutions plus propres et plus courtes sont les bienvenues

htafoya
la source
8
Pour aider les autres à l'avenir, envisageriez-vous de modifier le code dans votre réponse pour inclure votre getFields()méthode? Il ne doit pas être exact, juste un exemple avec peut-être juste quelques commentaires indiquant qu'il retourne un tableau d' EditTextobjets.
Squonk
14

Utilisez OnFocusChangeListener .

Par exemple:

editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        if (!hasFocus) {
            hideKeyboard();
        }
    }
});

Mise à jour : vous pouvez également remplacer onTouchEvent()votre activité et vérifier les coordonnées du toucher. Si les coordonnées sont en dehors de EditText, masquez le clavier.

Sergey Glotov
la source
9
Le problème est que l'edittext ne perd pas le focus lorsque je clique sur une étiquette ou d'autres vues qui ne sont pas focalisables.
htafoya du
Dans ce cas, j'ai une autre solution. J'ai mis à jour la réponse.
Sergey Glotov
2
onTouchEvent a appelé plusieurs fois, ce n'est donc pas une bonne pratique non plus
Jesus Dimrix
13

J'ai implémenté dispatchTouchEvent dans Activity pour ce faire:

private EditText mEditText;
private Rect mRect = new Rect();
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);

    int[] location = new int[2];
    mEditText.getLocationOnScreen(location);
    mRect.left = location[0];
    mRect.top = location[1];
    mRect.right = location[0] + mEditText.getWidth();
    mRect.bottom = location[1] + mEditText.getHeight();

    int x = (int) ev.getX();
    int y = (int) ev.getY();

    if (action == MotionEvent.ACTION_DOWN && !mRect.contains(x, y)) {
        InputMethodManager input = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        input.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
    }
    return super.dispatchTouchEvent(ev);
}

et je l'ai testé, fonctionne parfaitement!

Jishi Chen
la source
fonctionne, mais le problème est que si nous avons plus d'un EditText, nous devons également considérer cela, mais j'ai aimé votre réponse :-)
Lalit Poptani
getActionMasked (ev) est obsolète, alors utilisez maintenant: final int action = ev.getActionMasked (); pour la première ligne.
Andrew
12

Une manière plus Kotlin & Material Design en utilisant TextInputEditText (cette approche est également compatible avec EditTextView ) ...

1.Rendez la vue parent (vue du contenu de votre activité / fragment) cliquable et focalisable en ajoutant les attributs suivants

android:focusable="true"
android:focusableInTouchMode="true"
android:clickable="true"

2.Créez une extension pour toutes les vues (dans un fichier ViewExtension.kt par exemple):

fun View.hideKeyboard(){
    val inputMethodManager = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
    inputMethodManager.hideSoftInputFromWindow(this.windowToken, 0)
}

3.Créez un BaseTextInputEditText héritant de TextInputEditText. Implémentez la méthode onFocusChanged pour masquer le clavier lorsque la vue n'est pas focalisée:

class BaseTextInputEditText(context: Context?, attrs: AttributeSet?) : TextInputEditText(context, attrs){
    override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect)
        if (!focused) this.hideKeyboard()
    }
}

4.Appelez simplement votre nouvelle vue personnalisée dans votre XML:

<android.support.design.widget.TextInputLayout
        android:id="@+id/textInputLayout"
        ...>

        <com.your_package.BaseTextInputEditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            ... />

    </android.support.design.widget.TextInputLayout> 

C'est tout. Pas besoin de modifier vos contrôleurs (fragment ou activité) pour gérer ce cas répétitif.

Phil
la source
Ouais bien, mais j'aimerais qu'il y ait un moyen plus simple!
devDeejay
11

Remplacer le booléen public dispatchTouchEvent (événement MotionEvent) dans n'importe quelle activité (ou étendre la classe d'activité)

@Override
public boolean dispatchTouchEvent(MotionEvent event) {
    View view = getCurrentFocus();
    boolean ret = super.dispatchTouchEvent(event);

    if (view instanceof EditText) {
        View w = getCurrentFocus();
        int scrcoords[] = new int[2];
        w.getLocationOnScreen(scrcoords);
        float x = event.getRawX() + w.getLeft() - scrcoords[0];
        float y = event.getRawY() + w.getTop() - scrcoords[1];

        if (event.getAction() == MotionEvent.ACTION_UP 
 && (x < w.getLeft() || x >= w.getRight() 
 || y < w.getTop() || y > w.getBottom()) ) { 
            InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(getWindow().getCurrentFocus().getWindowToken(), 0);
        }
    }
 return ret;
}

Et c'est tout ce que vous devez faire

Hoang Trinh
la source
c'était le moyen le plus simple que j'ai trouvé pour le faire fonctionner. fonctionne avec plusieurs EditTexts et un ScrollView
RJH
Testé avec plusieurs EditTexts; Ça marche! Le seul inconvénient est que lorsque vous effectuez un mouvement de glissement, il se cache également.
RominaV
9

J'ai modifié la solution d'Andre Luis IM J'ai réalisé celle-ci:

J'ai créé une méthode utilitaire pour masquer le clavier virtuel de la même manière qu'André Luiz IM:

public static void hideSoftKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager)  activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
}

Mais au lieu d'enregistrer un OnTouchListener pour chaque vue, qui donne une mauvaise performance, j'ai enregistré le OnTouchListener pour juste la vue racine. Puisque l'événement bouillonne jusqu'à ce qu'il soit consommé (EditText est l'une des vues qui le consomme par défaut), s'il arrive à la vue racine, c'est parce qu'il n'a pas été consommé, donc je ferme le clavier virtuel.

findViewById(android.R.id.content).setOnTouchListener(new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        Utils.hideSoftKeyboard(activity);
        return false;
    }
});
Fernando Camargo
la source
1
Cela me semble plus sûr.
superarts.org
9

Je suis conscient que ce fil est assez ancien, la bonne réponse semble valide et il existe de nombreuses solutions de travail, mais je pense que l'approche indiquée ci-dessous pourrait avoir un avantage supplémentaire en termes d'efficacité et d'élégance.

J'ai besoin de ce comportement pour toutes mes activités, j'ai donc créé une classe CustomActivity héritant de la classe Activity et "accroché" la fonction dispatchTouchEvent . Il y a principalement deux conditions à respecter:

  1. Si le focus reste inchangé et que quelqu'un tape en dehors du champ de saisie actuel, fermez l'IME
  2. Si le focus a changé et que le prochain élément focalisé n'est pas une instance d'aucune sorte de champ de saisie, fermez l'IME

Voici mon résultat:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    if(ev.getAction() == MotionEvent.ACTION_UP) {
        final View view = getCurrentFocus();

        if(view != null) {
            final boolean consumed = super.dispatchTouchEvent(ev);

            final View viewTmp = getCurrentFocus();
            final View viewNew = viewTmp != null ? viewTmp : view;

            if(viewNew.equals(view)) {
                final Rect rect = new Rect();
                final int[] coordinates = new int[2];

                view.getLocationOnScreen(coordinates);

                rect.set(coordinates[0], coordinates[1], coordinates[0] + view.getWidth(), coordinates[1] + view.getHeight());

                final int x = (int) ev.getX();
                final int y = (int) ev.getY();

                if(rect.contains(x, y)) {
                    return consumed;
                }
            }
            else if(viewNew instanceof EditText || viewNew instanceof CustomEditText) {
                return consumed;
            }

            final InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

            inputMethodManager.hideSoftInputFromWindow(viewNew.getWindowToken(), 0);

            viewNew.clearFocus();

            return consumed;
        }
    }       

    return super.dispatchTouchEvent(ev);
}

Note latérale: De plus, j'attribue ces attributs à la vue racine, ce qui permet d'effacer le focus sur chaque champ de saisie et d'empêcher les champs de saisie de se concentrer sur le démarrage de l'activité (faisant de la vue du contenu le "capteur de focus"):

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    final View view = findViewById(R.id.content);

    view.setFocusable(true);
    view.setFocusableInTouchMode(true);
}
fje
la source
Super ça marche bien merci !! j'ai mis un vote positif pour votre réponse.
vijay
2
Je pense que c'est la meilleure solution pour les mises en page complexes. Mais j'ai trouvé 2 inconvénients jusqu'à présent: 1. Le menu contextuel EditText est impossible à cliquer - tout clic dessus entraîne une perte de focus de EditText 2. Lorsque notre EditText est en bas d'une vue et que nous cliquons longuement dessus (pour sélectionner le mot), puis après clavier montré que notre "point de clic" est sur le clavier, pas sur EditText - donc nous perdons à nouveau le focus: /
sosite
@sosite, je pense avoir abordé ces limites dans ma réponse; regarde.
Andy Dennie
6

J'ai aimé l'approche d'appels dispatchTouchEventfaite par htafoya, mais:

  • Je n'ai pas compris la partie minuterie (je ne sais pas pourquoi la mesure du temps d'arrêt devrait être nécessaire?)
  • Je n'aime pas enregistrer / désenregistrer tous les EditTexts à chaque changement de vue (il pourrait y avoir beaucoup de changements de vue et d'edittexts dans des hiérarchies complexes)

J'ai donc rendu cette solution un peu plus simple:

@Override
public boolean dispatchTouchEvent(final MotionEvent ev) {
    // all touch events close the keyboard before they are processed except EditText instances.
    // if focus is an EditText we need to check, if the touchevent was inside the focus editTexts
    final View currentFocus = getCurrentFocus();
    if (!(currentFocus instanceof EditText) || !isTouchInsideView(ev, currentFocus)) {
        ((InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE))
            .hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }
    return super.dispatchTouchEvent(ev);
}

/**
 * determine if the given motionevent is inside the given view.
 * 
 * @param ev
 *            the given view
 * @param currentFocus
 *            the motion event.
 * @return if the given motionevent is inside the given view
 */
private boolean isTouchInsideView(final MotionEvent ev, final View currentFocus) {
    final int[] loc = new int[2];
    currentFocus.getLocationOnScreen(loc);
    return ev.getRawX() > loc[0] && ev.getRawY() > loc[1] && ev.getRawX() < (loc[0] + currentFocus.getWidth())
        && ev.getRawY() < (loc[1] + currentFocus.getHeight());
}

Il y a un inconvénient:

Le passage de l'un EditTextà l'autre EditTextrend le clavier masqué et reshow - dans mon cas, c'est souhaité de cette façon, car cela montre que vous avez basculé entre deux composants d'entrée.

Christian R.
la source
Cette méthode a fonctionné le mieux en termes de capacité de plug-and-play avec mon FragmentActivity.
BillyRayCyrus
Merci, c'est la meilleure façon! J'ai également ajouté la vérification de l'action d'événement: int action = ev.getActionMasked (); if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {...}
sergey.n
Merci ! Vous avez économisé mon temps .. Meilleure réponse.
I.d007
6

Plaidoyer: Je reconnais que je n'ai aucun poids, mais veuillez prendre ma réponse au sérieux.

Problème: Ignorez le clavier logiciel lorsque vous cliquez en dehors du clavier ou modifiez le texte avec un code minimal.

Solution: bibliothèque externe connue sous le nom de Butterknife.

Solution en une ligne:

@OnClick(R.id.activity_signup_layout) public void closeKeyboard() { ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); }

Solution plus lisible:

@OnClick(R.id.activity_signup_layout) 
public void closeKeyboard() {
        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}

Explication: Liez l'écouteur OnClick à l'ID parent de la disposition XML de l'activité, de sorte que tout clic sur la disposition (et non sur le texte ou le clavier d'édition) exécutera cet extrait de code qui masquera le clavier.

Exemple: si votre fichier de mise en page est R.layout.my_layout et votre ID de mise en page est R.id.my_layout_id, votre appel de liaison Butterknife devrait ressembler à:

(@OnClick(R.id.my_layout_id) 
public void yourMethod {
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
}

Lien de documentation de Butterknife: http://jakewharton.github.io/butterknife/

Plug: Butterknife va révolutionner votre développement Android. Considère-le.

Remarque: Le même résultat peut être obtenu sans l'utilisation de la bibliothèque externe Butterknife. Définissez simplement un OnClickListener sur la disposition parent comme décrit ci-dessus.

Charles Woodson
la source
Impressionnant, solution parfaite! Je vous remercie.
nullforlife
6

En kotlin, nous pouvons faire ce qui suit. Pas besoin d'itérer toutes les vues. Cela fonctionnera également pour les fragments.

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    currentFocus?.let {
        val imm: InputMethodManager = getSystemService(
            Context.INPUT_METHOD_SERVICE
        ) as (InputMethodManager)
        imm.hideSoftInputFromWindow(it.windowToken, 0)
    }
    return super.dispatchTouchEvent(ev)
}
Sai
la source
ne fonctionne pas à partir d'un fragment
Nurseyit Tursunkulov
cela fonctionne mais il a un bug. par exemple, si je veux coller du texte dans la vue textuelle, le clavier se cache puis s'affiche. C'est un peu ennuyeux.
George Shalvashvili
Alors dites-moi la meilleure solution ..
Sai
4

Voici une autre variante de la réponse de fje qui répond aux problèmes soulevés par sosite.

L'idée ici est de gérer à la fois les actions vers le bas et vers le haut dans les activités dispatchTouchEvent méthode de . Sur l'action vers le bas, nous prenons note de la vue actuellement focalisée (le cas échéant) et si le toucher était à l'intérieur, en enregistrant ces deux informations pour plus tard.

Sur l'action ascendante, nous envoyons d'abord, pour permettre à une autre vue de prendre potentiellement le focus. Si après cela, la vue actuellement mise au point est la vue initialement mise au point et que le toucher vers le bas était à l'intérieur de cette vue, alors nous laissons le clavier ouvert.

Si la vue actuellement focalisée est différente de la vue initialement focalisée et que c'est un EditText, alors nous laissons également le clavier ouvert.

Sinon, nous le fermons.

Donc, pour résumer, cela fonctionne comme suit:

  • lorsque vous touchez l'intérieur d'un objet actuellement concentré EditText, le clavier reste ouvert
  • lors du passage d'un focus EditTextà un autre EditText, le clavier reste ouvert (ne se ferme pas / ne se rouvre pas)
  • lorsque vous touchez n'importe où en dehors d'un point actuellement focalisé EditTextqui n'est pas un autre EditText, le clavier se ferme
  • en appuyant longuement sur EditTextpour faire apparaître la barre d'action contextuelle (avec les boutons couper / copier / coller), le clavier reste ouvert, même si l'action UP s'est déroulée en dehors du focus EditText(qui s'est déplacée vers le bas pour faire de la place pour le CAB) . Notez, cependant, que lorsque vous appuyez sur un bouton dans le CAB, il fermera le clavier. Cela peut être souhaitable ou non; si vous voulez couper / copier d'un champ et coller dans un autre, ce serait le cas. Si vous souhaitez coller à nouveau dans le même EditText, ce ne serait pas.
  • lorsque le focus EditTextest en bas de l'écran et que vous cliquez longuement sur du texte pour le sélectionner, le EditTextfocus reste et donc le clavier s'ouvre comme vous le souhaitez, car nous faisons le "toucher est dans les limites de la vue" vérifier l'action vers le bas , pas l'action up.

    private View focusedViewOnActionDown;
    private boolean touchWasInsideFocusedView;
    
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                focusedViewOnActionDown = getCurrentFocus();
                if (focusedViewOnActionDown != null) {
                    final Rect rect = new Rect();
                    final int[] coordinates = new int[2];
    
                    focusedViewOnActionDown.getLocationOnScreen(coordinates);
    
                    rect.set(coordinates[0], coordinates[1],
                            coordinates[0] + focusedViewOnActionDown.getWidth(),
                            coordinates[1] + focusedViewOnActionDown.getHeight());
    
                    final int x = (int) ev.getX();
                    final int y = (int) ev.getY();
    
                    touchWasInsideFocusedView = rect.contains(x, y);
                }
                break;
    
            case MotionEvent.ACTION_UP:
    
                if (focusedViewOnActionDown != null) {
                    // dispatch to allow new view to (potentially) take focus
                    final boolean consumed = super.dispatchTouchEvent(ev);
    
                    final View currentFocus = getCurrentFocus();
    
                    // if the focus is still on the original view and the touch was inside that view,
                    // leave the keyboard open.  Otherwise, if the focus is now on another view and that view
                    // is an EditText, also leave the keyboard open.
                    if (currentFocus.equals(focusedViewOnActionDown)) {
                        if (touchWasInsideFocusedView) {
                            return consumed;
                        }
                    } else if (currentFocus instanceof EditText) {
                        return consumed;
                    }
    
                    // the touch was outside the originally focused view and not inside another EditText,
                    // so close the keyboard
                    InputMethodManager inputMethodManager =
                            (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    inputMethodManager.hideSoftInputFromWindow(
                        focusedViewOnActionDown.getWindowToken(), 0);
                    focusedViewOnActionDown.clearFocus();
    
                    return consumed;
                }
                break;
        }
    
        return super.dispatchTouchEvent(ev);
    }
Andy Dennie
la source
4

c'est trop simple, il suffit de rendre votre mise en page récente cliquable et focalisable par ce code:

android:id="@+id/loginParentLayout"
android:clickable="true"
android:focusableInTouchMode="true"

puis écrivez une méthode et un OnClickListner pour cette disposition, de sorte que lorsque la disposition la plus élevée est touchée, elle appelle une méthode dans laquelle vous écrivez du code pour ignorer le clavier. voici le code pour les deux; // vous devez écrire ceci dans OnCreate ()

 yourLayout.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View view) {
                    hideKeyboard(view);
                }
            });

méthode appelée depuis listner: -

 public void hideKeyboard(View view) {
     InputMethodManager imm =(InputMethodManager)getSystemService(Activity.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }
swarnim dixit
la source
4

Je trouve le bit de réponse accepté complexe pour cette simple exigence. Voici ce qui a fonctionné pour moi sans aucun problème.

findViewById(R.id.mainLayout).setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
            imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
            return false;
        }
    });
Joohay
la source
1
Si vous utilisez NestedScrollViewdes mises en page complexes ou complexes, consultez la réponse acceptée: stackoverflow.com/a/11656129/2914140 . Vous devez savoir que d'autres conteneurs peuvent consommer des touches.
CoolMind
3

Il existe une approche plus simple, basée sur le même problème avec l'iPhone. Remplacez simplement la disposition de l'arrière-plan lors de l'événement tactile, où le texte d'édition est contenu. Utilisez simplement ce code dans OnCreate de l'activité (login_fondo est la disposition racine):

    final LinearLayout llLogin = (LinearLayout)findViewById(R.id.login_fondo);
    llLogin.setOnTouchListener(
            new OnTouchListener()
            {
                @Override
                public boolean onTouch(View view, MotionEvent ev) {
                    InputMethodManager imm = (InputMethodManager) mActivity.getSystemService(
                            android.content.Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(mActivity.getCurrentFocus().getWindowToken(), 0);
                    return false;
                }
            });
Alex RR
la source
1
Comme je l'ai dit et je m'en souviens, cela ne fonctionne que si le formulaire n'est pas à l'intérieur d'un ScrollView.
htafoya
1
Cela ne fonctionne pas très bien si la disposition d'arrière-plan contient d'autres dispositions enfants.
AxeEffect
Merci d'avoir rappelé que onClick n'était pas la seule option.
Harpreet
3

Méthode pour afficher / masquer le clavier logiciel

InputMethodManager inputMethodManager = (InputMethodManager) currentActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
    if (isShow) {
        if (currentActivity.getCurrentFocus() == null) {
            inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
        } else {
            inputMethodManager.showSoftInput(currentActivity.getCurrentFocus(), InputMethodManager.SHOW_FORCED);    
        }

    } else {
        if (currentActivity.getCurrentFocus() == null) {
            inputMethodManager.toggleSoftInput(InputMethodManager.HIDE_NOT_ALWAYS, 0);
        } else {
            inputMethodManager.hideSoftInputFromInputMethod(currentActivity.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);    
        }

    }

J'espère qu'ils ont été utiles

lalosoft
la source
3

Celui-ci est la solution la plus simple pour moi (et élaborée par moi).

Il s'agit de la méthode pour masquer le clavier.

public void hideKeyboard(View view){
        if(!(view instanceof EditText)){
            InputMethodManager inputMethodManager=(InputMethodManager)getSystemService(INPUT_METHOD_SERVICE);
            inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(),0);
        }
    }

définissez maintenant l'attribut onclick de la disposition parent de l'activité sur la méthode ci-dessus à hideKeyboardpartir du mode Création de votre fichier XML ou en écrivant le code ci-dessous en mode Texte de votre fichier XML.

android:onClick="hideKeyboard"
NullByte08
la source
2

J'ai affiné la méthode, mis le code suivant dans une classe d'utilitaire d'interface utilisateur (de préférence, pas nécessairement) afin qu'il puisse être consulté à partir de toutes vos classes d'activité ou de fragment pour servir son objectif.

public static void serachAndHideSoftKeybordFromView(View view, final Activity act) {
    if(!(view instanceof EditText)) {
        view.setOnTouchListener(new View.OnTouchListener() {
            public boolean onTouch(View v, MotionEvent event) {
                hideSoftKeyboard(act);
                return false;
            }
        });
    }
    if (view instanceof ViewGroup) {
        for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
            View nextViewInHierarchy = ((ViewGroup) view).getChildAt(i);
            serachAndHideSoftKeybordFromView(nextViewInHierarchy, act);
        }
    }
}
public static void hideSoftKeyboard (Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    inputMethodManager.hideSoftInputFromWindow(activity.getCurrentFocus().getWindowToken(), 0);
}

Dites ensuite par exemple que vous devez l'appeler depuis l'activité, appelez-la comme suit;

UIutils.serachAndHideSoftKeybordFromView(findViewById(android.R.id.content), YourActivityName.this);

Remarquer

findViewById (android.R.id.content)

Cela nous donne la vue racine du groupe actuel (vous ne devez pas avoir défini l'identifiant sur la vue racine).

À votre santé :)

Uzair
la source
2

Activité

 @Override
 public boolean dispatchTouchEvent(MotionEvent ev) {
     ScreenUtils.hideKeyboard(this, findViewById(android.R.id.content).getWindowToken());
     return super.dispatchTouchEvent(ev);
 }

ScreenUtils

 public static void hideKeyboard(Context context, IBinder windowToken) {
     InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
     imm.hideSoftInputFromWindow(windowToken, InputMethodManager.HIDE_NOT_ALWAYS);
 }
icebail
la source
3
Ce code est simple, mais il a un problème évident: il ferme le clavier lorsque n'importe où est touché. C'est-à-dire que si vous touchez un emplacement différent de EditText pour déplacer le curseur d'entrée, il masque le clavier et le clavier réapparaît par le système.
Damn Vegetables
2

Ajoutez simplement ce code dans la classe @Overide

public boolean dispatchTouchEvent(MotionEvent ev) {
    View view = getCurrentFocus();
    if (view != null && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText && !view.getClass().getName().startsWith("android.webkit.")) {
        int scrcoords[] = new int[2];
        view.getLocationOnScreen(scrcoords);
        float x = ev.getRawX() + view.getLeft() - scrcoords[0];
        float y = ev.getRawY() + view.getTop() - scrcoords[1];
        if (x < view.getLeft() || x > view.getRight() || y < view.getTop() || y > view.getBottom())
            ((InputMethodManager)this.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow((this.getWindow().getDecorView().getApplicationWindowToken()), 0);
    }
    return super.dispatchTouchEvent(ev);
}
Haseeb Javed
la source
3
Bien que cela puisse répondre à la question, il est préférable d'expliquer les parties essentielles de la réponse et peut-être quel était le problème avec le code OPs.
pirho
oui @pirho, je suis également d'accord avec vous Haseeb doit se concentrer sur la bonne réponse.
Dilip
2
@Dilip Saviez-vous que vous pouvez également voter pour des commentaires que vous acceptez? C'est juste pour garder la section des commentaires propre afin de ne pas avoir beaucoup de commentaires ayant le même point.
pirho
2

Au lieu d'itérer dans toutes les vues ou de remplacer dispatchTouchEvent.

Pourquoi ne pas simplement remplacer onUserInteraction () de l'activité, cela garantira que le clavier sera ignoré chaque fois que l'utilisateur tapera en dehors de EditText.

Fonctionne même lorsque EditText est à l'intérieur de scrollView.

@Override
public void onUserInteraction() {
    if (getCurrentFocus() != null) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
    }
}
SKG
la source
c'est la meilleure réponse
ovluca
Oui! C'est une réponse super nette. Et si vous créez une AbstractActivity que toutes vos autres activités étendent, vous pouvez en faire un comportement par défaut dans toute votre application.
wildcat12
1

Je l'ai fait fonctionner avec une légère variante de la solution de Fernando Camarago. Dans ma méthode onCreate, j'attache un seul onTouchListener à la vue racine mais envoie la vue plutôt que l'activité comme argument.

        findViewById(android.R.id.content).setOnTouchListener(new OnTouchListener() {           
        public boolean onTouch(View v, MotionEvent event) {
            Utils.hideSoftKeyboard(v);
            return false;
        }
    });

Dans une classe d'Utils séparée est ...

    public static void hideSoftKeyboard(View v) {
    InputMethodManager imm = (InputMethodManager) v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 
    imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
AndyMc
la source
1

Cela peut être ancien, mais j'ai réussi à le faire en impliquant une classe personnalisée

public class DismissKeyboardListener implements OnClickListener {

    Activity mAct;

    public DismissKeyboardListener(Activity act) {
        this.mAct = act;
    }

    @Override
    public void onClick(View v) {
        if ( v instanceof ViewGroup ) {
            hideSoftKeyboard( this.mAct );
        }
    }       
}

public void hideSoftKeyboard(Activity activity) {
        InputMethodManager imm = (InputMethodManager)
        getSystemService(Activity.INPUT_METHOD_SERVICE);
        imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
}

la meilleure pratique consiste à créer une classe d'assistance et chaque disposition relative / linéaire des conteneurs doit l'implémenter.

**** Notez que seul le conteneur principal doit implémenter cette classe (pour l'optimisation) ****

et l'implémenter comme ceci:

Parent.setOnClickListener( new DismissKeyboardListener(this) ); 

le mot-clé c'est pour l'activité. donc si vous êtes sur un fragment que vous utilisez comme getActivity ();

--- bravo si ça vous aide ... --- bravo Ralph ---

ralphgabb
la source
1

Il s'agit d'une version légèrement modifiée de la réponse de fje qui fonctionnait pour la plupart parfaitement.

Cette version utilise ACTION_DOWN, donc effectuer une action de défilement ferme également le clavier. Il ne propage pas non plus l'événement sauf si vous cliquez sur un autre EditText. Cela signifie que cliquer n'importe où en dehors de votre EditText, même sur un autre cliquable, ferme simplement le clavier.

@Override
public boolean dispatchTouchEvent(MotionEvent ev)
{
    if(ev.getAction() == MotionEvent.ACTION_DOWN)
    {
        final View view = getCurrentFocus();

        if(view != null)
        {
            final View viewTmp = getCurrentFocus();
            final View viewNew = viewTmp != null ? viewTmp : view;

            if(viewNew.equals(view))
            {
                final Rect rect = new Rect();
                final int[] coordinates = new int[2];

                view.getLocationOnScreen(coordinates);

                rect.set(coordinates[0], coordinates[1], coordinates[0] + view.getWidth(), coordinates[1] + view.getHeight());

                final int x = (int) ev.getX();
                final int y = (int) ev.getY();

                if(rect.contains(x, y))
                {
                    super.dispatchTouchEvent(ev);
                    return true;
                }
            }
            else if(viewNew instanceof EditText || viewNew instanceof CustomEditText)
            {
                super.dispatchTouchEvent(ev);
                return true;
            }

            final InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);

            inputMethodManager.hideSoftInputFromWindow(viewNew.getWindowToken(), 0);

            viewNew.clearFocus();

            return true;
        }
    }
    return super.dispatchTouchEvent(ev);
}
Cadmonkey33
la source
Quelque chose ne regarde pas ici; vous affectez à la fois viewet viewTmpà getCurrentFocus(), donc ils auront toujours la même valeur.
Andy Dennie
1

J'ai fait comme ça:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
   View view = getCurrentFocus();
   if (view != null && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_MOVE) && view instanceof EditText && !view.getClass().getName().startsWith("android.webkit.")) {
            int scrcoords[] = new int[2];
            view.getLocationOnScreen(scrcoords);
            float x = ev.getRawX() + view.getLeft() - scrcoords[0];
            float y = ev.getRawY() + view.getTop() - scrcoords[1];
            if (x < view.getLeft() || x > view.getRight() || y < view.getTop() || y > view.getBottom())
                hideKeyboard(this);
        }
    return super.dispatchTouchEvent(ev);
}

Masquer le code du clavier :

public static void hideKeyboard(Activity act) {
    if(act!=null)
      ((InputMethodManager)act.getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow((act.getWindow().getDecorView().getApplicationWindowToken()), 0);
  }

Terminé

Hiren Patel
la source