Comment capturer l'événement «affichage / masquage du clavier virtuel» dans Android?

Réponses:

69

Remarque

Cette solution ne fonctionnera pas pour les claviers logiciels et onConfigurationChangedne sera pas appelée pour les claviers logiciels (virtuels).


Vous devez gérer vous-même les modifications de configuration.

http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange

Échantillon:

// from the link above
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);


    // Checks whether a hardware keyboard is available
    if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show();
    } else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show();
    }
}

Ensuite, modifiez simplement la visibilité de certaines vues, mettez à jour un champ et modifiez votre fichier de mise en page.

Pedro Loureiro
la source
4
@shiami try newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO~ Chris
cimnine
3
Cela ne fonctionne que si vous avez enregistré l'activité pour écouter les changements de configuration souhaités dans le AndroidManifest.
Raul Agrait
66
veuillez mettre à jour votre réponse et indiquer qu'elle ne fonctionne pas pour le clavier logiciel. J'ai perdu ma demi-journée à essayer votre code. Et puis vu ces commentaires.
Shirish Herwade
17
Cela ne fonctionne pas pour les claviers "virtuels", ce qui était la question d'origine.
brummfondel
18
Eh bien, la question portait sur le SOFT KEYBOARD, pourquoi la réponse acceptée concernant un clavier matériel? -1 !
Denys Vitali
56

Ce n'est peut-être pas la solution la plus efficace. Mais cela a fonctionné pour moi à chaque fois ... J'appelle cette fonction partout où j'ai besoin d'écouter le softKeyboard.

boolean isOpened = false;

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

            int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
            if (heightDiff > 100) { // 99% of the time the height diff will be due to a keyboard.
                Toast.makeText(getApplicationContext(), "Gotcha!!! softKeyboardup", 0).show();

                if (isOpened == false) {
                    //Do two things, make the view top visible and the editText smaller
                }
                isOpened = true;
            } else if (isOpened == true) {
                Toast.makeText(getApplicationContext(), "softkeyborad Down!!!", 0).show();
                isOpened = false;
            }
        }
    });
}

Remarque: Cette approche entraînera des problèmes si l'utilisateur utilise un clavier flottant.

amalBit
la source
1
addOnGlobalLayoutListener?
coolcool1994
7
Cela sent comme une fuite de mémoire. Vous ajoutez un écouteur à un objet global, qui vous tiendra et ne vous laissera jamais partir.
flexicious.com
9
Celui-ci ne fonctionnera pas non plus pour les activités définies avec android:windowSoftInputMode="adjustPan"ou adjustResizeavec une fenêtre plein écran, car la disposition n'est jamais redimensionnée.
Ionoclast Brigham
1
Cela ne fonctionne qu'avec adjustResize. Pour adjustPan, le heightDiff ne change jamais.
alexhilton
2
pourquoi comparez- vous un booléen?
Xerus
37

Si vous souhaitez gérer l'affichage / masquage de la fenêtre du clavier IMM (virtuel) de votre activité, vous devrez sous-classer votre disposition et remplacer la méthode onMesure (afin de pouvoir déterminer la largeur mesurée et la hauteur mesurée de votre disposition). Après cela, définissez la disposition sous-classée comme vue principale pour votre activité par setContentView (). Vous pourrez désormais gérer les événements de la fenêtre d'affichage / masquage IMM. Si cela semble compliqué, ce n'est pas vraiment ça. Voici le code:

main.xml

   <?xml version="1.0" encoding="utf-8"?>
   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal" >
        <EditText
             android:id="@+id/SearchText" 
             android:text="" 
             android:inputType="text"
             android:layout_width="fill_parent"
             android:layout_height="34dip"
             android:singleLine="True"
             />
        <Button
             android:id="@+id/Search" 
             android:layout_width="60dip"
             android:layout_height="34dip"
             android:gravity = "center"
             />
    </LinearLayout>

Maintenant, dans votre activité, déclarez la sous-classe de votre mise en page (main.xml)

    public class MainSearchLayout extends LinearLayout {

    public MainSearchLayout(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("Search Layout", "Handling Keyboard Window shown");

        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();

        if (actualHeight > proposedheight){
            // Keyboard is shown

        } else {
            // Keyboard is hidden
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

Vous pouvez voir dans le code que nous gonflons la disposition pour notre constructeur d'activité dans la sous-classe

inflater.inflate(R.layout.main, this);

Et maintenant, définissez simplement la vue du contenu de la disposition sous-classée pour notre activité.

public class MainActivity extends Activity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        MainSearchLayout searchLayout = new MainSearchLayout(this, null);

        setContentView(searchLayout);
    }

    // rest of the Activity code and subclassed layout...

}
Nebojsa Tomcic
la source
3
Je dois enquêter davantage, mais j'ai des doutes quant à savoir si cela fonctionnerait dans mon cas pour une petite boîte de dialogue sur un grand écran pour lequel les mesures de disposition ne seraient pas affectées par la présence d'un clavier.
PJL
4
Cela ne fonctionne pas pour Android: windowSoftInputMode = "adjustPan". Je voulais que mon écran ne se rétrécisse pas après l'apparition du clavier virtuel. Pouvez-vous s'il vous plaît dire n'importe quel correctif pour qu'il fonctionne même pour adjustPan
Shirish Herwade
Cela ne fonctionne pas, cela va toujours à la partie else ici si (actualHeight> propositionheight) {// Le clavier est affiché} else {// Le clavier est caché}
Aamirkhan
Vous pouvez également utiliser une vue personnalisée avec cette même idée, suit un exemple gist.github.com/juliomarcos/8ca307cd7eca607c8547
Julio Rodrigues
1
Ne fonctionnera pas pour les activités définies avec android:windowSoftInputMode="adjustPan"ou adjustResizeavec une fenêtre plein écran, car la disposition n'est jamais redimensionnée.
Ionoclast Brigham
35

J'ai fait comme ça:

Ajouter une OnKeyboardVisibilityListenerinterface.

public interface OnKeyboardVisibilityListener {
    void onVisibilityChanged(boolean visible);
}

HomeActivity.java :

public class HomeActivity extends Activity implements OnKeyboardVisibilityListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_sign_up);
    // Other stuff...
    setKeyboardVisibilityListener(this);
}

private void setKeyboardVisibilityListener(final OnKeyboardVisibilityListener onKeyboardVisibilityListener) {
    final View parentView = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
    parentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        private boolean alreadyOpen;
        private final int defaultKeyboardHeightDP = 100;
        private final int EstimatedKeyboardDP = defaultKeyboardHeightDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);
        private final Rect rect = new Rect();

        @Override
        public void onGlobalLayout() {
            int estimatedKeyboardHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, parentView.getResources().getDisplayMetrics());
            parentView.getWindowVisibleDisplayFrame(rect);
            int heightDiff = parentView.getRootView().getHeight() - (rect.bottom - rect.top);
            boolean isShown = heightDiff >= estimatedKeyboardHeight;

            if (isShown == alreadyOpen) {
                Log.i("Keyboard state", "Ignoring global layout change...");
                return;
            }
            alreadyOpen = isShown;
            onKeyboardVisibilityListener.onVisibilityChanged(isShown);
        }
    });
}


@Override
public void onVisibilityChanged(boolean visible) {
    Toast.makeText(HomeActivity.this, visible ? "Keyboard is active" : "Keyboard is Inactive", Toast.LENGTH_SHORT).show();
  }
}

J'espère que cela vous aidera.

Hiren Patel
la source
2
Merci Hiren. C'est la solution parfaite +1
Harin Kaklotar
1
Merci, a travaillé pour moi! Si vous souhaitez simplement ajuster votre RecyclerView, voir la solution ici: stackoverflow.com/a/43204258/373106
David Papirov
1
Implémentation réutilisable parfaite, travaillée dans Activity ou Fragment, merci
Pelanes
1
ty vraiment sympa.
ZaoTaoBao
@DavidPapirov, vous avez collé un lien vers un RecyclerView, mais vous n'en avez pas parlé ici.
CoolMind
22

Sur la base du code de Nebojsa Tomcic, j'ai développé la sous-classe RelativeLayout suivante:

import java.util.ArrayList;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

public class KeyboardDetectorRelativeLayout extends RelativeLayout {

    public interface IKeyboardChanged {
        void onKeyboardShown();
        void onKeyboardHidden();
    }

    private ArrayList<IKeyboardChanged> keyboardListener = new ArrayList<IKeyboardChanged>();

    public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyboardDetectorRelativeLayout(Context context) {
        super(context);
    }

    public void addKeyboardStateChangedListener(IKeyboardChanged listener) {
        keyboardListener.add(listener);
    }

    public void removeKeyboardStateChangedListener(IKeyboardChanged listener) {
        keyboardListener.remove(listener);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();

        if (actualHeight > proposedheight) {
            notifyKeyboardShown();
        } else if (actualHeight < proposedheight) {
            notifyKeyboardHidden();
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void notifyKeyboardHidden() {
        for (IKeyboardChanged listener : keyboardListener) {
            listener.onKeyboardHidden();
        }
    }

    private void notifyKeyboardShown() {
        for (IKeyboardChanged listener : keyboardListener) {
            listener.onKeyboardShown();
        }
    }

}

Cela fonctionne très bien ... Mark, que cette solution ne fonctionnera que lorsque le mode d'entrée douce de votre activité est défini sur "WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE"

Stefan
la source
3
Cela ne fonctionne pas pour Android: windowSoftInputMode = "adjustPan". Je voulais que mon écran ne se rétrécisse pas après l'apparition du clavier virtuel. Pouvez-vous s'il vous plaît dire n'importe quel correctif pour qu'il fonctionne même pour adjustPan
Shirish Herwade
1
Celui-ci ne fonctionnera pas non plus pour les activités définies avec android:windowSoftInputMode="adjustPan"ou adjustResizeavec une fenêtre plein écran, car la disposition n'est jamais redimensionnée.
Ionoclast Brigham
il se déclenche plusieurs fois.
zionpi
22

Comme la réponse de @ amalBit, enregistrez un auditeur dans la mise en page globale et calculez la différence entre le fond visible de dectorView et le fond proposé, si la différence est supérieure à une certaine valeur (hauteur devinée de l'IME), nous pensons que l'IME est en hausse:

    final EditText edit = (EditText) findViewById(R.id.edittext);
    edit.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (keyboardShown(edit.getRootView())) {
                Log.d("keyboard", "keyboard UP");
            } else {
                Log.d("keyboard", "keyboard Down");
            }
        }
    });

private boolean keyboardShown(View rootView) {

    final int softKeyboardHeight = 100;
    Rect r = new Rect();
    rootView.getWindowVisibleDisplayFrame(r);
    DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
    int heightDiff = rootView.getBottom() - r.bottom;
    return heightDiff > softKeyboardHeight * dm.density;
}

le seuil de hauteur 100 est la hauteur minimale supposée d'IME.

Cela fonctionne à la fois pour adjustPan et adjustResize.

alexhilton
la source
2
Je suis sur le point de me tirer les cheveux !! Vous avez sauvé mes cheveux;)
Vijay Singh Chouhan
1
C est la seule bonne réponse ici, cela fonctionne sur un clavier souple parfait, merci
Z3nk
12

La solution de Nebojsa a presque fonctionné pour moi. Lorsque j'ai cliqué à l'intérieur d'un EditText sur plusieurs lignes, il savait que le clavier était affiché, mais quand j'ai commencé à taper à l'intérieur de EditText, actualHeight et proposeHeight étaient toujours les mêmes, donc il ne savait pas qu'ils étaient toujours affichés. J'ai fait une légère modification pour stocker la hauteur maximale et cela fonctionne bien. Voici la sous-classe révisée:

public class CheckinLayout extends RelativeLayout {

    private int largestHeight;

    public CheckinLayout(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.checkin, this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        largestHeight = Math.max(largestHeight, getHeight());

        if (largestHeight > proposedheight)
            // Keyboard is shown
        else
            // Keyboard is hidden

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
Gary Foster
la source
10

Je ne sais pas si quelqu'un poste cela. Trouvé cette solution simple à utiliser! . La classe SoftKeyboard se trouve sur gist.github.com . Mais alors que le rappel clavier / masqué des événements nous avons besoin d'un gestionnaire pour faire correctement les choses sur l'interface utilisateur:

/*
Somewhere else in your code
*/
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use your root layout
InputMethodManager im = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE);

/*
Instantiate and pass a callback
*/
SoftKeyboard softKeyboard;
softKeyboard = new SoftKeyboard(mainLayout, im);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{

    @Override
    public void onSoftKeyboardHide() 
    {
        // Code here
        new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    // Code here will run in UI thread
                    ...
                }
            });
    }

    @Override
    public void onSoftKeyboardShow() 
    {
        // Code here
        new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    // Code here will run in UI thread
                    ...
                }
            });

    }   
});
Robert
la source
voici le Git pour obtenir SoftkeyBoard " gist.github.com/felHR85/… "
douarbou
9

Je résous ce problème en remplaçant onKeyPreIme (int keyCode, événement KeyEvent) dans mon EditText personnalisé.

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
        //keyboard will be hidden
    }
}
qbait
la source
Comment l'utiliser dans Fragment ou Activity? @Qbait
Maulik Dodia
Cela ne fonctionne pas, il ne peut être appelé que lorsque je quitte la page dans mon cas.
DysaniazzZ
c'est la méthode de EditText, voir cette réponse: stackoverflow.com/a/5993196/2093236
Dmide
4

J'ai une sorte de hack pour faire ça. Bien qu'il ne semble pas y avoir de moyen de détecter quand le clavier virtuel est affiché ou masqué, vous pouvez en fait détecter quand il est sur le point d'être affiché ou masqué en définissant un OnFocusChangeListenersur le EditTextque vous écoutez.

EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
    {
        @Override
        public void onFocusChange(View view, boolean hasFocus)
        {
            //hasFocus tells us whether soft keyboard is about to show
        }
    });

REMARQUE: Une chose à savoir avec ce hack est que ce rappel est déclenché immédiatement lorsque le EditTextgain ou la perte de focus. Cela se déclenchera juste avant que le clavier virtuel ne s'affiche ou ne se cache. La meilleure façon que j'ai trouvée de faire quelque chose après que le clavier s'affiche ou se cache est d'utiliser un Handleret de retarder quelque chose ~ 400 ms, comme ceci:

EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
    {
        @Override
        public void onFocusChange(View view, boolean hasFocus)
        {
            new Handler().postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        //do work here
                    }
                }, 400);
        }
    });
poisondminds
la source
1
Ça ne marche pas, sinon. OnFocusChangeListenerindique uniquement si le EditTextfocus a été activé après le changement d'état. Mais le IMEpeut être caché quand EditTexta focus, comment détecter ce cas?
DysaniazzZ
2

J'ai résolu le problème du codage arrière de la visualisation de texte sur une seule ligne.

package com.helpingdoc;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;

public class MainSearchLayout extends LinearLayout {
    int hieght = 0;
    public MainSearchLayout(Context context, AttributeSet attributeSet) {

        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);


    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("Search Layout", "Handling Keyboard Window shown");
       if(getHeight()>hieght){
           hieght = getHeight();
       }
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();
        System.out.println("....hieght = "+ hieght);
        System.out.println("....actualhieght = "+ actualHeight);
        System.out.println("....proposedheight = "+ proposedheight);
        if (actualHeight > proposedheight){
            // Keyboard is shown


        } else if(actualHeight<proposedheight){
            // Keyboard is hidden

        }

        if(proposedheight == hieght){
             // Keyboard is hidden
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
user2462737
la source
2
Cela ne fonctionne pas pour Android: windowSoftInputMode = "adjustPan". Je voulais que mon écran ne se rétrécisse pas après l'apparition du clavier virtuel. Pouvez-vous s'il vous plaît dire n'importe quel correctif pour qu'il fonctionne même pour adjustPan
Shirish Herwade
Lorsque la fonction cache / affiche, cette méthode d'écoute appelle deux ou trois fois. Je ne sais pas quel est exactement le problème.
Jagveer Singh Rajput
2

Vous pouvez également vérifier le rembourrage inférieur du premier enfant DecorView. Il sera réglé sur une valeur non nulle lorsque le clavier sera affiché.

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    View view = getRootView();
    if (view != null && (view = ((ViewGroup) view).getChildAt(0)) != null) {
        setKeyboardVisible(view.getPaddingBottom() > 0);
    }
    super.onLayout(changed, left, top, right, bottom);
}
MatrixDev
la source
1

Masquer | Afficher les événements pour le clavier peut être écouté via un simple hack dans OnGlobalLayoutListener:

 final View activityRootView = findViewById(R.id.top_root);
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();

                if (heightDiff > 100) {
                    // keyboard is up
                } else {
                    // keyboard is down
                }
            }
        });

Ici, activityRootView est la vue racine de votre activité.

Varun Verma
la source
mon heightDiff est de 160 au début et 742 avec kbd, j'ai donc dû introduire et définir initialHeightDiff au début
djdance
0

en utilisant viewTreeObserver pour obtenir facilement l'événement clavier.

layout_parent.viewTreeObserver.addOnGlobalLayoutListener {
            val r = Rect()
            layout_parent.getWindowVisibleDisplayFrame(r)
            if (layout_parent.rootView.height - (r.bottom - r.top) > 100) { // if more than 100 pixels, its probably a keyboard...
                Log.e("TAG:", "keyboard open")
            } else {
                Log.e("TAG:", "keyboard close")
            }
        }

** layout_parent est votre point de vue commeedit_text.parent

Geet Thakur
la source
-2

La réponse de Nebojsa Tomcic ne m'a pas été utile. J'ai RelativeLayoutavec TextViewet à l' AutoCompleteTextViewintérieur. Je dois faire défiler TextViewvers le bas lorsque le clavier est affiché et lorsqu'il est masqué. Pour ce faire, j'ai remplacé la onLayoutméthode et cela fonctionne bien pour moi.

public class ExtendedLayout extends RelativeLayout
{
    public ExtendedLayout(Context context, AttributeSet attributeSet)
    {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        super.onLayout(changed, l, t, r, b);

        if (changed)
        {
            int scrollEnd = (textView.getLineCount() - textView.getHeight() /
                textView.getLineHeight()) * textView.getLineHeight();
            textView.scrollTo(0, scrollEnd);
        }
    }
}
Mode
la source