Comment vérifier la visibilité du clavier logiciel dans Android?

516

Je dois faire une chose très simple: savoir si le clavier logiciel est affiché. Est-ce possible sur Android?

fhucho
la source
9
Bien que la réponse de Reuben Scratton soit bonne, elle semble cassée sur une tablette. J'ai remplacé le chèque diff> 128 par diff> screenHeight / 3.
kingston
2
La réponse de Reuben Scratton était bonne mais j'avais besoin de l'ajustement de KaChi pour l'utiliser réellement.
Cullan
1
Pourquoi Google ne fait pas fonctionner une méthode intégrée standard pour toutes les applications de clavier?
Fruit
4
Ça me fait encore frire, que ce n'est pas un système qui fonctionne ...
longi
1
Il est absolument fou que cette API soit toujours manquante 10 ans plus tard . Je suis très content de m'être éloigné d'Android.
Reuben Scratton

Réponses:

674

NOUVELLE RÉPONSE ajoutée le 25 janvier 2012

Depuis que j'ai écrit la réponse ci-dessous, quelqu'un m'a informé de l'existence de ViewTreeObserver et de ses amis, des API qui se cachent dans le SDK depuis la version 1.

Plutôt que d'exiger un type de mise en page personnalisé, une solution beaucoup plus simple consiste à donner à la vue racine de votre activité un ID connu, par exemple @+id/activityRoot, raccordez un GlobalLayoutListener au ViewTreeObserver, et à partir de là, calculez la différence de taille entre la racine de la vue de votre activité et la taille de la fenêtre:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
        if (heightDiff > dpToPx(this, 200)) { // if more than 200 dp, it's probably a keyboard...
            // ... do something here
        }
     }
});

Utilisation d'un utilitaire tel que:

public static float dpToPx(Context context, float valueInDp) {
    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
}

Facile!

Remarque: Votre application doit définir ce drapeau dans le manifeste Android, android:windowSoftInputMode="adjustResize"sinon la solution ci-dessus ne fonctionnera pas.

RÉPONSE ORIGINALE

Oui, c'est possible, mais c'est beaucoup plus difficile qu'il ne devrait l'être.

Si je dois me soucier de l'apparition et de la disparition du clavier (ce qui est assez fréquent), je personnalise ma classe de disposition de niveau supérieur en une qui remplace onMeasure(). La logique de base est que si la mise en page se trouve remplir considérablement moins que la surface totale de la fenêtre, alors un clavier virtuel s'affiche probablement.

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.LinearLayout;

/*
 * LinearLayoutThatDetectsSoftKeyboard - a variant of LinearLayout that can detect when 
 * the soft keyboard is shown and hidden (something Android can't tell you, weirdly). 
 */

public class LinearLayoutThatDetectsSoftKeyboard extends LinearLayout {

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

    public interface Listener {
        public void onSoftKeyboardShown(boolean isShowing);
    }
    private Listener listener;
    public void setListener(Listener listener) {
        this.listener = listener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = MeasureSpec.getSize(heightMeasureSpec);
        Activity activity = (Activity)getContext();
        Rect rect = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        int statusBarHeight = rect.top;
        int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
        int diff = (screenHeight - statusBarHeight) - height;
        if (listener != null) {
            listener.onSoftKeyboardShown(diff>128); // assume all soft keyboards are at least 128 pixels high
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);       
    }

    }

Ensuite, dans votre classe d'activité ...

public class MyActivity extends Activity implements LinearLayoutThatDetectsSoftKeyboard.Listener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        LinearLayoutThatDetectsSoftKeyboard mainLayout = (LinearLayoutThatDetectsSoftKeyboard)findViewById(R.id.main);
        mainLayout.setListener(this);
        ...
    }


    @Override
    public void onSoftKeyboardShown(boolean isShowing) {
        // do whatever you need to do here
    }

    ...
}
Reuben Scratton
la source
62
Cela ne fonctionnait pas pour moi jusqu'à ce que je réalise que vous devez définir l'attribut suivant sur votre activité: android: windowSoftInputMode = "adjustResize"
ajh158
9
Semble faire l'affaire. De plus, si vous ne connaissez pas l'ID de la vue racine, voici comment obtenir la vue:((ViewGroup) findViewById(android.R.id.content)).getChildAt(0)
Goldsmith
8
Si vous essayez ceci en utilisant la vue racine réelle ( android.R.id.content), vous pourrez dire avec plus de certitude que Systeml'entité change sa hauteur plutôt que votre application. Il serait beaucoup plus sûr pour l'équipe Android de nous donner une pause et de nous faire savoir au moins des choses de base sur l'entrée SoftKeyboard.
Graeme
14
Attention, cela heightDiffinclura toujours la hauteur de la barre d'action. Dans la nouvelle réponse qui a été ignorée en testant si cette hauteur est supérieure à une constante, mais 100 pixels ne sont pas suffisants pour les appareils xxhdpi tels que le Nexus 4. Envisagez de convertir cette valeur en DP si vous voulez vraiment utiliser ce travail hacky- environ.
Paul Lammertsma
8
Remarque: ne fonctionne pas avec WindowManager.LayoutParams.FLAG_FULLSCREEN et avec un thème plein écran.
VAV
303

J'espère donc que cela aide quelqu'un.

La nouvelle réponse donnée par Reuben Scratton est excellente et vraiment efficace, mais elle ne fonctionne vraiment que si vous définissez votre windowSoftInputMode sur adjustResize. Si vous le définissez sur adjustPan, il n'est toujours pas possible de détecter si le clavier est visible ou non à l'aide de son extrait de code. Pour contourner ce problème, j'ai apporté cette petite modification au code ci-dessus.

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new 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 > 0.25*activityRootView.getRootView().getHeight()) { // if more than 25% of the screen, its probably a keyboard...
        ... do something here
    }
 }
}); 
Kachi
la source
1
C'est celui qui a fonctionné pour moi. J'essayais de détecter l'état du clavier à partir d'une personnalisation TwoDScrollerViewsimilaire à stackoverflow.com/a/5224088/530513 bien qu'avec le zoom aussi. L'enfant n'était pas une disposition simple ImageViewmais personnalisée (étend RelativeLayout) mais n'a pas pu détecter le clavier en utilisant la solution recommandée malgré le réglage android:windowSoftInputMode="adjustResize". Merci!
David O'Meara
1
Merci merci merci! adjustResize n'est tout simplement pas viable pour mon application et votre solution a parfaitement fonctionné.
dwemthy
1
Fonctionne avec ActionBaret ActionBarSherlock. Merci beaucoup! Soit dit en passant, il existe une méthode r.height():)
Dmitry Zaytsev
1
Je vais joindre une prime ici dans les 23 heures pour marquer cette réponse en quelque sorte.
Dmitry Zaytsev
9
heightDiff > root.getRootView().getHeight() / 4est une bonne valeur pour travailler avec un appareil haute résolution. 100px est trop court. dans Nexus 5 avec 1080x1920 res, 1920 - (996-75)>? 100 = 999 1920 - (1776-75)>? 100 = 219 // le clavier est en place dans la galaxie s2 avec 480x800 res, 800 - (800-38)>? 100 = 38 800 - (410-38)>? 100 = 428 // le clavier est en place donc le nombre magique 100px n'est pas assez bon.
Flask_KR
55

Cela a toujours été le cas en termes informatiques, mais cette question est toujours incroyablement pertinente!

J'ai donc pris les réponses ci-dessus et les ai combinées et affinées un peu ...

public interface OnKeyboardVisibilityListener {


    void onVisibilityChanged(boolean visible);
}

public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
    final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);

    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        private boolean wasOpened;

        private final int DefaultKeyboardDP = 100;

        // From @nathanielwolf answer...  Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
        private final int EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);

        private final Rect r = new Rect();

        @Override
        public void onGlobalLayout() {
            // Convert the dp to pixels.
            int estimatedKeyboardHeight = (int) TypedValue
                    .applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, activityRootView.getResources().getDisplayMetrics());

            // Conclude whether the keyboard is shown or not.
            activityRootView.getWindowVisibleDisplayFrame(r);
            int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
            boolean isShown = heightDiff >= estimatedKeyboardHeight;

            if (isShown == wasOpened) {
                Log.d("Keyboard state", "Ignoring global layout change...");
                return;
            }

            wasOpened = isShown;
            listener.onVisibilityChanged(isShown);
        }
    });
}

Travaille pour moi :)

REMARQUE: Si vous remarquez que le DefaultKeyboardDP ne correspond pas à votre appareil, jouez avec la valeur et postez un commentaire pour que tout le monde sache quelle devrait être la valeur ... nous obtiendrons finalement la valeur correcte pour s'adapter à tous les appareils!

Pour plus de détails, consultez l'implémentation sur Cyborg

TacB0sS
la source
2
+1 Merci beaucoup !! J'essayais les autres réponses, mais elles ne fonctionnent pas. Puis j'ai trouvé le vôtre et ça marche comme un charme. Code génial! : D
Kevin van Mierlo
cela ne fonctionne que si vous ajoutez: android: windowSoftInputMode = "stateHidden | adjustPan" ou android: windowSoftInputMode = "stateHidden | adjustResize" Merci !!!!
Lena Bru
Êtes-vous sûr? Si les serveurs de mémoire, j'ai obtenu les événements correctement également lorsque l'androïde: windowSoftInputMode avait sa valeur par défaut ... la seule chose qui n'a pas fonctionné est le comportement de l'écran, donc je l'ai
rétréci
2
une petite correction: private final int EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT> = Build.VERSION_CODES.LOLLIPOP? 48: 0);
binaryKarmic
2
Excellent!! Peu importe que "windowSoftInputMode" soit réglé sur "adjustPan" / "adjustResize" / "adjustPan | stateHidden" / "adjustResize | stateHidden", ou même sans cette option, cela fonctionne toujours! Testé sur XiaoMi 8.
Zhou Hongbo
52

Désolé pour la réponse tardive, mais j'avais créé une petite classe d'aide pour gérer les événements d'ouverture / fermeture avec des auditeurs avertis et d'autres choses utiles, peut-être que quelqu'un le trouverait utile:

import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;

import java.util.LinkedList;
import java.util.List;

public class SoftKeyboardStateWatcher implements ViewTreeObserver.OnGlobalLayoutListener {

    public interface SoftKeyboardStateListener {
        void onSoftKeyboardOpened(int keyboardHeightInPx);
        void onSoftKeyboardClosed();
    }

    private final List<SoftKeyboardStateListener> listeners = new LinkedList<SoftKeyboardStateListener>();
    private final View activityRootView;
    private int        lastSoftKeyboardHeightInPx;
    private boolean    isSoftKeyboardOpened;

    public SoftKeyboardStateWatcher(View activityRootView) {
        this(activityRootView, false);
    }

    public SoftKeyboardStateWatcher(View activityRootView, boolean isSoftKeyboardOpened) {
        this.activityRootView     = activityRootView;
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    public void onGlobalLayout() {
        final Rect r = new Rect();
        //r will be populated with the coordinates of your view that area still visible.
        activityRootView.getWindowVisibleDisplayFrame(r);

        final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
        if (!isSoftKeyboardOpened && heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
            isSoftKeyboardOpened = true;
            notifyOnSoftKeyboardOpened(heightDiff);
        } else if (isSoftKeyboardOpened && heightDiff < 100) {
            isSoftKeyboardOpened = false;
            notifyOnSoftKeyboardClosed();
        }
    }

    public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) {
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
    }

    public boolean isSoftKeyboardOpened() {
        return isSoftKeyboardOpened;
    }

    /**
     * Default value is zero {@code 0}.
     *
     * @return last saved keyboard height in px
     */
    public int getLastSoftKeyboardHeightInPx() {
        return lastSoftKeyboardHeightInPx;
    }

    public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.add(listener);
    }

    public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.remove(listener);
    }

    private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) {
        this.lastSoftKeyboardHeightInPx = keyboardHeightInPx;

        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardOpened(keyboardHeightInPx);
            }
        }
    }

    private void notifyOnSoftKeyboardClosed() {
        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardClosed();
            }
        }
    }
}

Exemple d'utilisation:

final SoftKeyboardStateWatcher softKeyboardStateWatcher 
    = new SoftKeyboardStateWatcher(findViewById(R.id.activity_main_layout);

// Add listener
softKeyboardStateWatcher.addSoftKeyboardStateListener(...);
// then just handle callbacks
Artem Zinnatullin
la source
2
La classe n'est pas petite mais l'implémentation est sûrement :). Merci je vais essayer ça :)
Atul O Holic
1
Envoyez-moi un ping si vous avez des problèmes :) J'ai réussi à l'utiliser dans 2 projets
Artem Zinnatullin
Après avoir essayé de nombreux exemples ci-dessus et rencontré des problèmes mineurs, celui-ci était celui qui fonctionnait le mieux pour moi sur de nombreux appareils différents (y compris xxhdpi). De plus, il est dans sa propre classe réutilisable. Je l'ai converti pour être utilisé en mono droïde.
kheit
Certains claviers, parfois, ont une rangée supplémentaire de touches personnalisées en haut (par exemple, des mots prédits). Ceux-ci ne font apparemment pas partie du clavier lui-même, car l'utilisation de getLastKeyboardHeightInPx()ne comprend pas la hauteur de cette ligne. Connaissez-vous également un moyen de le prendre en compte?
ygesher
Cela ne fonctionne que si vous êtes prêt à compromettre le changement de hauteur de mise en page lorsque le clavier apparaît. droite?
M. Usman Khan
34

Quelques améliorations pour éviter de détecter à tort la visibilité du clavier logiciel sur les appareils haute densité:

  1. Le seuil de différence de hauteur doit être défini comme 128 dp , pas 128 pixels .
    Reportez-vous au document de conception Google sur les mesures et la grille , 48 dp est une taille confortable pour un objet tactile et 32 dp est un minimum pour les boutons. Le clavier virtuel générique doit comprendre 4 rangées de boutons clés, la hauteur minimale du clavier doit donc être: 32 dp * 4 = 128 dp , ce qui signifie que la taille du seuil doit être transférée aux pixels en multipliant la densité de l'appareil. Pour les périphériques xxxhdpi (densité 4), le seuil de hauteur du clavier virtuel doit être de 128 * 4 = 512 pixels.

  2. Différence de hauteur entre la vue racine et sa zone visible:
    hauteur de la vue racine - hauteur de la barre d'état - hauteur du cadre visible = bas de la vue racine - bas du cadre visible, car la hauteur de la barre d'état est égale au haut du cadre visible de la vue racine.

    private final String TAG = "TextEditor";
    private TextView mTextEditor;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_editor);
        mTextEditor = (TextView) findViewById(R.id.text_editor);
        mTextEditor.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                isKeyboardShown(mTextEditor.getRootView());
            }
        });
    }
    
    private boolean isKeyboardShown(View rootView) {
        /* 128dp = 32dp * 4, minimum button height 32dp and generic 4 rows soft keyboard */
        final int SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD = 128;
    
        Rect r = new Rect();
        rootView.getWindowVisibleDisplayFrame(r);
        DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
        /* heightDiff = rootView height - status bar height (r.top) - visible frame height (r.bottom - r.top) */
        int heightDiff = rootView.getBottom() - r.bottom;
        /* Threshold size: dp to pixels, multiply with display density */
        boolean isKeyboardShown = heightDiff > SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD * dm.density;
    
        Log.d(TAG, "isKeyboardShown ? " + isKeyboardShown + ", heightDiff:" + heightDiff + ", density:" + dm.density
                + "root view height:" + rootView.getHeight() + ", rect:" + r);
    
        return isKeyboardShown;
    }
Orchard Cafe
la source
4
Cela mérite d'être la réponse acceptée. Ignorer la densité m'a donné des résultats très différents sur des appareils avec des facteurs de forme différents mais des tailles de pixels similaires. Merci!
Ginger McMurray
2
Merci ... c'est un meilleur état!
TacB0sS
1
Excellent. La méthode isKeyboardShown () est ce dont nous avons besoin. Merci
Danylo Volokh
Cela fonctionne également en 2020. Réponse parfaite. J'ai essayé tout le code mais cela fonctionne parfaitement.
Mitesh Jain
8

J'ai utilisé un peu de temps pour comprendre cela ... Je l'ai exécuté quelques CastExceptions, mais j'ai compris que vous pouvez remplacer LinearLayout dans le fichier layout.xml par le nom de la classe.

Comme ça:

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/llMaster">

<com.ourshoppingnote.RelativeLayoutThatDetectsSoftKeyboard android:background="@drawable/metal_background"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:id="@+id/rlMaster" >
    <LinearLayout android:layout_width="fill_parent"
        android:layout_height="1dip" android:background="@drawable/line"></LinearLayout>

          ....

</com.ourshoppingnote.RelativeLayoutThatDetectsSoftKeyboard>    


</LinearLayout>

De cette façon, vous ne rencontrez aucun problème de distribution.

... et si vous ne voulez pas le faire sur chaque page, je vous recommande d'utiliser "MasterPage dans Android". Voir le lien ici: http://jnastase.alner.net/archive/2011/01/08/ldquomaster-pagesrdquo-in-android.aspx

Janus Kamp Hansen
la source
Wow soyez prudent lorsque vous collez ceci dans votre XML si vous n'avez pas le même nom de package / classe. Eclipse décide simplement de geler et vous devez l'arrêter. Un tel produit professionnel. / s
Spencer Ruport
5
@SpencerRuport, c'est pourquoi c'est gratuit.
Cody
@DoctorOreo - obtenez IntelliJ. C'est gratuit et ça ne craint pas.
Mark
@Mark - quelques mois après avoir posté cela, j'ai effectivement essayé IntelliJ. C'est beaucoup mieux, OMI, qu'Eclipse. Tous leurs produits (pour la plupart) sont, je pense, excellents. J'en ai même acheté quelques-uns.
Cody
Désolé d'avoir réactivé un tel ancien fil de commentaires. Je suis heureux que vous l'utilisiez et que vous l'appréciiez. J'adore utiliser IntelliJ ainsi que AppCode pour iOS et PyCharm pour Python. À votre santé!
Mark
5

L'idée est que si vous devez masquer votre clavier et vérifier simultanément l'état de la saisie logicielle, utilisez la solution suivante:

public boolean hideSoftInput() {
    InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
    return imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);
}

Cette méthode renvoie true si le clavier a été affiché avant de se cacher.

George Maisuradze
la source
C'est celui qui fonctionne sans utiliser la hauteur et tout ... Merci ... vous
avez
4

J'ai trouvé qu'une combinaison de la méthode de @ Reuben_Scratton avec la méthode de @ Yogesh semble fonctionner le mieux. La combinaison de leurs méthodes donnerait quelque chose comme ceci:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
  @Override
  public void onGlobalLayout() {
    if (getResources().getConfiguration().keyboardHidden == Configuration.KEYBOARDHIDDEN_NO) { // Check if keyboard is not hidden
       // ... do something here
    }
  }
});
cbradley
la source
toujours Configuration.KEYBOARDHIDDEN_NO.
fantouch
4

Vous pouvez observer le masquage du clavier logiciel à l'aide de décorView de l'activité.

public final class SoftKeyboardUtil {
    public static final String TAG = "SoftKeyboardUtil";
    public static void observeSoftKeyBoard(Activity activity , final OnSoftKeyBoardHideListener listener){
        final View decorView = activity.getWindow().getDecorView();
        decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect rect = new Rect();
                decorView.getWindowVisibleDisplayFrame(rect);
                int displayHight = rect.bottom - rect.top;
                int hight = decorView.getHeight();
                boolean hide = (double)displayHight / hight > 0.8 ;
                if(Log.isLoggable(TAG, Log.DEBUG)){
                    Log.d(TAG ,"DecorView display hight = "+displayHight);
                    Log.d(TAG ,"DecorView hight = "+ hight);
                    Log.d(TAG, "softkeyboard visible = " + !hide);
                }

                listener.onSoftKeyBoardVisible(!hide);

            }
        });
    }



    public interface OnSoftKeyBoardHideListener{
        void onSoftKeyBoardVisible(boolean visible);
    }
}
Zebulon Li
la source
4

Au lieu de supposer la différence de codage, j'ai fait quelque chose comme ça, car j'avais des options de menu dans mon application.

final View root= findViewById(R.id.myrootview); 
root.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
    public void onGlobalLayout() {
        int heightDiff = root.getRootView().getHeight() - root.getHeight();

        Rect rectgle= new Rect();
        Window window= getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
        int contentViewTop=                     
          window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
        if(heightDiff <= contentViewTop){
            //Soft KeyBoard Hidden
        }else{
            //Soft KeyBoard Shown
        }
     }
});
Santhosh Shettigar
la source
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
4

Il existe également une solution avec des insertions système, mais cela ne fonctionne qu'avec API >= 21( Android L). Supposons que vous en ayez un BottomNavigationView, LinearLayoutet que vous devez le masquer lorsque le clavier s'affiche:

> LinearLayout
  > ContentView
  > BottomNavigationView

Tout ce que vous devez faire est d'étendre LinearLayoutde cette manière:

public class KeyboardAwareLinearLayout extends LinearLayout {
    public KeyboardAwareLinearLayout(Context context) {
        super(context);
    }

    public KeyboardAwareLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyboardAwareLinearLayout(Context context,
                                     @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public KeyboardAwareLinearLayout(Context context, AttributeSet attrs,
                                     int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        int childCount = getChildCount();
        for (int index = 0; index < childCount; index++) {
            View view = getChildAt(index);
            if (view instanceof BottomNavigationView) {
                int bottom = insets.getSystemWindowInsetBottom();
                if (bottom >= ViewUtils.dpToPx(200)) {
                    // keyboard is shown
                    view.setVisibility(GONE);
                } else {
                    // keyboard is hidden
                    view.setVisibility(VISIBLE);
                }
            }
        }
        return insets;
    }
}

L'idée est que lorsque le clavier est affiché, les insertions système sont modifiées avec une .bottomvaleur assez importante .

nikis
la source
4

Il y a une méthode cachée qui peut aider pour cela InputMethodManager.getInputMethodWindowVisibleHeight,. Mais je ne sais pas pourquoi c'est caché.

import android.content.Context
import android.os.Handler
import android.view.inputmethod.InputMethodManager

class SoftKeyboardStateWatcher(private val ctx: Context) {
  companion object {
    private const val DELAY = 10L
  }

  private val handler = Handler()
  private var isSoftKeyboardOpened: Boolean = false

  private val height: Int
    get() {
      val imm = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
      val method = imm.javaClass.getMethod("getInputMethodWindowVisibleHeight")
      method.isAccessible = true
      return method.invoke(imm) as Int
    }

  private val task: Runnable by lazy {
    Runnable {
      start()
      if (!isSoftKeyboardOpened && height > 0) {
        isSoftKeyboardOpened = true
        notifyOnSoftKeyboardOpened(height)
      } else if (isSoftKeyboardOpened && height == 0) {
        isSoftKeyboardOpened = false
        notifyOnSoftKeyboardClosed()
      }
    }
  }

  var listener: SoftKeyboardStateListener? = null

  interface SoftKeyboardStateListener {
    fun onSoftKeyboardOpened(keyboardHeightInPx: Int)
    fun onSoftKeyboardClosed()
  }

  fun start() {
    handler.postDelayed(task, DELAY)
  }

  fun stop() {
    handler.postDelayed({
      if (!isSoftKeyboardOpened) handler.removeCallbacks(task)
    }, DELAY * 10)
  }

  private fun notifyOnSoftKeyboardOpened(keyboardHeightInPx: Int) {
    listener?.onSoftKeyboardOpened(keyboardHeightInPx)
  }

  private fun notifyOnSoftKeyboardClosed() {
    listener?.onSoftKeyboardClosed()
  }
}
Kevin Du
la source
Si quelqu'un en a besoin - cela fonctionne également dans Xamarin, le nom de la méthode est exactement le même et doit être accessible de la même manière - via la propriété Class sur InputMethodManager.
Konstantin Severy
Soyez prudent en utilisant cela, c'est une API non prise en charge (elle est cachée pour une raison) et pour commencer, cela ne fonctionne pas sur KitKat.
Daniele Ricci
3

Aucune de ces solutions ne fonctionnera pour Lollipop en l'état. Dans Lollipop activityRootView.getRootView().getHeight()comprend la hauteur de la barre de boutons, tandis que la mesure de la vue ne le fait pas. J'ai adapté la solution la meilleure / la plus simple ci-dessus pour travailler avec Lollipop.

    final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new 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);
    Resources res = getResources();
    // The status bar is 25dp, use 50dp for assurance
    float maxDiff =
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, res.getDisplayMetrics());

    //Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      float buttonBarHeight =
          TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, res.getDisplayMetrics());
      maxDiff += buttonBarHeight;
    }
    if (heightDiff > maxDiff) { // if more than 100 pixels, its probably a keyboard...
      ...do something here
    }
  }
});
nathanielwolf
la source
Pourquoi une solution similaire de stackoverflow.com/a/18992807/2914140 n'a-t-elle pas fonctionné pour vous et en quoi votre solution diffère-t-elle?
CoolMind
3

Je viens de rencontrer un bogue en utilisant la plupart des solutions ci-dessus qui suggèrent d'ajouter un numéro fixe.

Le S4 a un dpi élevé, ce qui fait que la hauteur de la barre de navigation est de 100 pixels, donc mon application pense que le clavier est ouvert tout le temps.

Donc, avec tous les nouveaux téléphones haute résolution sortis, je pense que l'utilisation d'une valeur codée en dur n'est pas une bonne idée à long terme.

Une meilleure approche que j'ai trouvée après quelques tests sur divers écrans et appareils était d'utiliser le pourcentage. Obtenez la différence entre decorView et le contenu de votre application et vérifiez ensuite quel est le pourcentage de cette différence. D'après les statistiques que j'ai obtenues, la plupart des barres de navigation (quelle que soit la taille, la résolution, etc.) occuperont entre 3% et 5% de l'écran. Là où, comme si le clavier était ouvert, il occupait entre 47% et 55% de l'écran.

En conclusion, ma solution était de vérifier si le différentiel est supérieur à 10%, alors je suppose que c'est un clavier ouvert.

N Jay
la source
3

J'ai utilisé une légère variante de la réponse de Reuban, qui s'est avérée plus utile dans certaines circonstances, en particulier avec les appareils haute résolution.

final View activityRootView = findViewById(android.R.id.content);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
        new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int heightView = activityRootView.getHeight();
                int widthView = activityRootView.getWidth();
                if (1.0 * widthView / heightView > 3) {
                    //Make changes for Keyboard not visible
                } else {
                    //Make changes for keyboard visible
                }
            }
        });
PearsonArtPhoto
la source
qu'est-ce que R.id.activityRoot
ranjith
2
au lieu de créer et d'utiliser R.id.activityRoot, vous pouvez simplement utiliser android.R.id.contentce qui est exactement ce dont vous avez besoin.
Marcin Orlowski
3

Cela a toujours été le cas pour l'ordinateur, mais cette question est toujours incroyablement pertinente! J'ai donc pris les réponses ci-dessus et les ai combinées et affinées un peu ...

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

public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
    final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        private boolean wasOpened;

    private final Rect r = new Rect();

        @Override
        public void onGlobalLayout() {
            activityRootView.getWindowVisibleDisplayFrame(r);

            int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
            boolean isOpen = heightDiff > 100;
            if (isOpen == wasOpened) {
                logDebug("Ignoring global layout change...");
                return;
            }

            wasOpened = isOpen;
            listener.onVisibilityChanged(isOpen);
        }
    });
}

Ça marche pour moi.

Roselyn Soffer
la source
3

Essaye ça:

final View activityRootView = getWindow().getDecorView().getRootView();
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 < activityRootView.getRootView().getHeight() / 4 ) { // if more than 100 pixels, its probably a keyboard...
             // ... do something here ... \\
        }
    }
});
Ofek Ashery
la source
2

Ma réponse est fondamentalement la même que la réponse de Kachi, mais je l'ai enveloppée dans une belle classe d'aide pour nettoyer la façon dont elle est utilisée dans toute mon application.

import android.app.Activity;
import android.app.Fragment;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;

/**
 * Detects Keyboard Status changes and fires events only once for each change
 */
public class KeyboardStatusDetector {
    KeyboardVisibilityListener visibilityListener;

    boolean keyboardVisible = false;

    public void registerFragment(Fragment f) {
        registerView(f.getView());
    }

    public void registerActivity(Activity a) {
        registerView(a.getWindow().getDecorView().findViewById(android.R.id.content));
    }

    public KeyboardStatusDetector registerView(final View v) {
        v.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect r = new Rect();
                v.getWindowVisibleDisplayFrame(r);

                int heightDiff = v.getRootView().getHeight() - (r.bottom - r.top);
                if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
                    /** Check this variable to debounce layout events */
                    if(!keyboardVisible) {
                        keyboardVisible = true;
                        if(visibilityListener != null) visibilityListener.onVisibilityChanged(true);
                    }
                } else {
                    if(keyboardVisible) {
                        keyboardVisible = false;
                        if(visibilityListener != null) visibilityListener.onVisibilityChanged(false);
                    }
                }
            }
        });

        return this;
    }

    public KeyboardStatusDetector setVisibilityListener(KeyboardVisibilityListener listener) {
        visibilityListener = listener;
        return this;
    }

    public static interface KeyboardVisibilityListener {
        public void onVisibilityChanged(boolean keyboardVisible);
    }
}

Vous pouvez l'utiliser pour détecter les modifications du clavier n'importe où dans l'application, comme ceci:

    new KeyboardStatusDetector()
            .registerFragment(fragment)  //register to a fragment 
            .registerActivity(activity)  //or register to an activity
            .registerView(view)          //or register to a view
            .setVisibilityListener(new KeyboardVisibilityListener() {
                @Override
                public void onVisibilityChanged(boolean keyboardVisible) {
                    if(keyboardVisible) {
                       //Do stuff for keyboard visible
                    }else {
                       //Do stuff for keyboard hidden
                    }
                }
            });

Remarque: n'utilisez qu'un seul des appels "enregistrer". Ils fonctionnent tous de la même manière et ne sont là que pour plus de commodité

billylindeman
la source
2

vous pouvez essayer cela, fonctionne très bien pour moi:

InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);

if (imm.isAcceptingText()) {
    //Software Keyboard was shown..
} else {
    //Software Keyboard was not shown..
}
IRvanFauziE
la source
1
Ne l'utilisez pas, cela retourne vrai si le clavier a été caché en utilisant le dos mais la vue est focalisée, au moins sur Marshmallow
Maragues
2
répond toujours ce qui est visible
jose920405
2

J'avais du mal à maintenir l'état du clavier lors du changement d'orientation des fragments dans un viseur. Je ne sais pas pourquoi, mais cela semble juste chancelant et agit différemment d'une activité standard.

Pour conserver l'état du clavier dans ce cas, vous devez d'abord ajouter android:windowSoftInputMode = "stateUnchanged"à votre AndroidManifest.xml. Vous remarquerez peut-être, cependant, que cela ne résout pas réellement tout le problème - le clavier ne s'est pas ouvert pour moi s'il a été précédemment ouvert avant le changement d'orientation. Dans tous les autres cas, le comportement semblait correct.

Ensuite, nous devons mettre en œuvre l'une des solutions mentionnées ici. Le plus propre que j'ai trouvé était celui de George Maisuradze - utilisez le rappel booléen de hideSoftInputFromWindow:

InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
return imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);

J'ai stocké cette valeur dans la onSaveInstanceStateméthode de mon fragment et je l'ai récupérée onCreate. Ensuite, j'ai montré de force le clavier onCreateViews'il avait une valeur de true(il retourne vrai si le clavier est visible avant de le cacher avant la destruction du fragment).

Point quantique
la source
1

Voici ma solution, et cela fonctionne. Au lieu de rechercher la taille des pixels, vérifiez simplement que la hauteur de la vue du contenu a changé ou non:

// Scroll to the latest comment whenever the keyboard is shown
commentsContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        private int oldHeight;

        @Override
        public void onGlobalLayout() {
            int newHeight = commentsContent.getMeasuredHeight();
            if (newHeight < oldHeight) {
                // Check for the keyboard showing in case the height difference
                // is a result of orientation change
                if (isSoftKeyboardShowing(CommentsActivity.this)) {
                    // Keyboard is showing so scroll to the latest comment
                    scrollToLatestComment();
                }
            }
            oldHeight = newHeight;
        }

    });


public static boolean isSoftKeyboardShowing(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    return inputMethodManager.isActive();
}
Méchant homme
la source
inputMethodManager.isActive () renvoie toujours vrai pour moi, que le clavier soit en place ou non
EionRobb
1

Ne créez pas de code dur. Le meilleur moyen est de redimensionner vos vues pendant que vous vous concentrez sur EditText avec KeyBord Show. Vous pouvez le faire en ajoutant la propriété de redimensionnement de l'activité dans le fichier manifeste à l'aide du code ci-dessous.

android:windowSoftInputMode="adjustResize"

Rahul Mandaliya
la source
1

Il existe une méthode directe pour le savoir. Et, il ne nécessite aucune modification de mise en page.
Donc, cela fonctionne également en mode plein écran immersif.

L'astuce est que vous essayez de masquer ou d'afficher le clavier virtuel et de capturer le résultat de cet essai.
Pas de panique, cela ne montre ni ne masque vraiment le clavier. Nous demandons simplement l'État.

Pour rester à jour, vous pouvez simplement répéter l'opération, par exemple toutes les 200 millisecondes, à l'aide d'un gestionnaire.

Vous trouverez une implémentation ici: https://stackoverflow.com/a/27567074/2525452

fies
la source
1

Je pense que cette méthode vous aidera à découvrir si le clavier est visible ou non.

 public Boolean isSoftKeyBoardVisible(){
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);

    if (imm.isAcceptingText()) {
        Log.d(TAG,"Software Keyboard was shown");
        return true;
    } else {
        Log.d(TAG,"Software Keyboard was not shown");
        return false;
    }

}
John Smith
la source
répond toujours ce qui est visible
jose920405
0

La nouvelle réponse de Reuben Scratton (calculer le HeightDiff int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();) ne fonctionnera pas en activité si vous définissez le mode de barre d'état translucide.

si vous utilisez la barre d'état translucide, activityRootView.getHeight()ne changera jamais la météo, le clavier logiciel est visible. il renverra toujours la hauteur de l'activité et la barre d'état.

Par exemple, Nexus 4, Android 5.0.1, défini android:windowTranslucentStatussur true, il renverra 1184 pour toujours, même si l'image s'est ouverte. Si vous définissez android:windowTranslucentStatussur false, il renverra la hauteur correctement, s'il est invisible, il renverra 1134 (n'inclut pas la barre d'état)

Je ne sais pas s'il s'agit d'un bug, j'ai essayé 4.4.4 et 5.0.1, le résultat est le même.

Donc, jusqu'à présent, la deuxième réponse la plus acceptée, la solution de Kachi sera le moyen le plus sûr de calculer la hauteur d'IME. En voici une copie:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new        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...
    ... do something here
    }
 }
}); 
Loyea
la source
0

Une méthode qui n'a pas besoin d'un LayoutListener

Dans mon cas, je voudrais sauvegarder l'état du clavier avant de remplacer mon Fragment. J'appelle la méthode hideSoftInputFromWindow à partir onSaveInstanceState, qui ferme le clavier et retourne - moi si le clavier est visible ou non.

Cette méthode est simple mais peut changer l'état de votre clavier.

Gordak
la source
0

Je sais que c'est un vieux post mais je pense que c'est l'approche la plus simple que je connaisse et mon appareil de test est le Nexus 5. Je ne l'ai pas essayé sur d'autres appareils. J'espère que d'autres partageront leur approche s'ils trouvent que mon code n'est pas bon :)

public static boolean isKeyboardShown(Context context, View view) {
        if (context == null || view == null) {
            return false;
        }
        InputMethodManager imm = (InputMethodManager) context
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        return imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 
}

imm.hideSoftInputFromWindow renvoie un booléen.

Merci,

Fran Ceriu
la source
0
if (keyopen())
{
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY,0);            
}

La fonction ci-dessus est ce que j'utilise pour vérifier si un clavier est visible. Si c'est le cas, je le ferme.

Ci-dessous montre les deux méthodes requises.

Tout d'abord, définissez la hauteur de fenêtre réalisable dans onCreate.

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

//  add to onCreate method
    Rect rectgle= new Rect();
    Window window= getWindow();
    window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
    sheight= rectgle.bottom;
//

} 

Ensuite, ajoutez une méthode booléenne qui obtient la hauteur de la fenêtre à cette instance. S'il ne correspond pas à l'original (en supposant que vous ne le modifiez pas en cours de route ...), le clavier est ouvert.

public boolean keyopen()
{
    Rect rectgle= new Rect();
    Window window= getWindow();
    window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
    int curheight= rectgle.bottom;

    if (curheight!=sheight)
    {
        return true;
    }
    else
    {
        return false;
    }
}

Frotz!

Belboz
la source
0

Je sais à quel point vous pouvez déterminer si le clavier est caché ou non.

public int getStatusBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

public int getNavigationBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

public boolean isKeyboardHidden() {
    int delta = mRootView.getRootView().getHeight() - mRootView.getHeight() - getNavigationBarHeight() - getStatusBarHeight()
            - getSupportActionBar().getHeight();
    return delta <= 0;
}

Cela fonctionne pour les tablettes. Lorsque la barre de navigation est affichée horizontalement.

Valentin Baryshev
la source