¿Cómo verificar la visibilidad del teclado de software en Android?

516

Necesito hacer algo muy simple: averiguar si se muestra el teclado del software. ¿Es esto posible en Android?

fhucho
fuente
99
Aunque la respuesta de Reuben Scratton es buena, parece rota en una tableta. Reemplacé el check diff> 128 con diff> screenHeight / 3.
Kingston
2
La respuesta de Reuben Scratton fue buena, pero exigí el ajuste de KaChi para usarla realmente.
Cullan
1
¿Por qué Google no hace que un método integrado estándar funcione para todas las aplicaciones de teclado?
Fruta
44
Todavía me hermanos, que este no es un sistema funciona ...
longi
1
Es absolutamente loco que esta API todavía falte 10 años después . Muy contento de haberme alejado de Android.
Reuben Scratton

Respuestas:

674

NUEVA RESPUESTA agregada el 25 de enero de 2012

Desde que escribí la respuesta a continuación, alguien me dio a conocer la existencia de ViewTreeObserver y sus amigos, API que han estado al acecho en el SDK desde la versión 1.

En lugar de requerir un tipo de diseño personalizado, una solución mucho más simple es dar a la vista raíz de su actividad un ID conocido, por ejemplo @+id/activityRoot, enganchar un GlobalLayoutListener en el ViewTreeObserver, y desde allí calcular la diferencia de tamaño entre la raíz de la vista de la actividad y el tamaño de la ventana:

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

Usando una utilidad como:

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

¡Fácil!

Nota: Su aplicación debe establecer este indicador en el Manifiesto de Android; de lo android:windowSoftInputMode="adjustResize"contrario, la solución anterior no funcionará.

RESPUESTA ORIGINAL

Sí, es posible, pero es mucho más difícil de lo que debería ser.

Si necesito preocuparme cuando el teclado aparece y desaparece (lo cual es bastante frecuente), entonces lo que hago es personalizar mi clase de diseño de nivel superior en una que anule onMeasure(). La lógica básica es que si el diseño se encuentra llenando significativamente menos que el área total de la ventana, entonces probablemente se muestre un teclado virtual.

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

    }

Luego, en tu clase de actividad ...

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
fuente
6262
Esto no funcionó para mí hasta que me di cuenta de que debe establecer el siguiente atributo en su actividad: android: windowSoftInputMode = "ajustarResize"
ajh158
99
Parece estar haciendo el truco. Además, si no conoce el ID de la vista raíz, así es como puede obtener la vista:((ViewGroup) findViewById(android.R.id.content)).getChildAt(0)
Goldsmith
8
Si intenta esto utilizando la vista raíz real ( android.R.id.content), podrá decir con mayor confianza que, en Systemlugar de su aplicación, la entidad está cambiando su altura. Sería mucho más seguro para el equipo de Android darnos un descanso y hacernos saber al menos cosas básicas sobre la entrada de SoftKeyboard.
Graeme
14
Tenga en cuenta que heightDiffsiempre incluirá la altura de la barra de acción. En la nueva respuesta que se ha ignorado al probar si esa altura es mayor que alguna constante, pero 100 píxeles no son suficientes para dispositivos xxhdpi como el Nexus 4. Considere convertir ese valor a DP si realmente desea utilizar este trabajo extravagante. alrededor.
Paul Lammertsma
8
Aviso: no funciona con WindowManager.LayoutParams.FLAG_FULLSCREEN y con un tema de pantalla completa.
VAV
303

Espero que esto ayude a alguien.

La nueva respuesta que dio Reuben Scratton es excelente y realmente eficiente, pero realmente solo funciona si configura su windowSoftInputMode para ajustarResize. Si lo configura para ajustarPan, aún no es posible detectar si el teclado es visible o no con su fragmento de código. Para evitar esto, hice esta pequeña modificación al código anterior.

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
fuente
1
Este es el que funcionó para mí. Intenté detectar el estado del teclado desde una costumbre TwoDScrollerViewsimilar a stackoverflow.com/a/5224088/530513 aunque también con el zoom. El niño no era un ImageViewdiseño simple sino personalizado (se extiende RelativeLayout) pero no pudo detectar el teclado usando la solución recomendada a pesar de la configuración android:windowSoftInputMode="adjustResize". ¡Gracias!
David O'Meara
1
¡Gracias, gracias, gracias! ajustarResize simplemente no es viable para mi aplicación y su solución funcionó perfectamente.
dwemthy
1
Trabaja con ActionBary ActionBarSherlock. ¡Muchas gracias! Por cierto, hay un método r.height():)
Dmitry Zaytsev
1
Adjuntaré una recompensa aquí dentro de las 23 horas para marcar esta respuesta de alguna manera.
Dmitry Zaytsev
99
heightDiff > root.getRootView().getHeight() / 4Es un buen valor para trabajar con dispositivos de alta resolución. 100 px es muy corto. en Nexus 5 con resolución de 1080x1920, 1920 - (996-75)>? 100 = 999 1920 - (1776-75)>? 100 = 219 // el teclado está funcionando en galaxy s2 con 480x800 res, 800 - (800-38)>? 100 = 38 800 - (410-38)>? 100 = 428 // el teclado está arriba, así que el número mágico 100px no es lo suficientemente bueno.
Flask_KR
55

Ha sido una eternidad en términos de computadora, ¡pero esta pregunta sigue siendo increíblemente relevante!

Así que he tomado las respuestas anteriores y las he combinado y refinado un poco ...

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

Funciona para mi :)

NOTA: Si nota que DefaultKeyboardDP no se ajusta a su dispositivo, juegue con el valor y publique un comentario para que todos sepan cuál debería ser el valor ... ¡finalmente obtendremos el valor correcto para todos los dispositivos!

Para más detalles, consulte la implementación en Cyborg

TacB0sS
fuente
2
+1 ¡Muchas gracias! Estaba probando las otras respuestas, pero no funcionan. Luego encontré el tuyo y funciona a las mil maravillas. Código impresionante! : D
Kevin van Mierlo
esto funciona solo si agrega: android: windowSoftInputMode = "stateHidden | ajustarPan" o android: windowSoftInputMode = "stateHidden | ajustarResize" ¡Gracias!
Lena Bru
¿Estás seguro? Si los servidores de memoria, obtuve los eventos correctamente también cuando el android: windowSoftInputMode tenía su valor predeterminado ... lo único que no funcionó es el comportamiento de la pantalla, así que lo reduje manualmente ...
TacB0sS
2
una pequeña corrección: privado final int EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT> = Build.VERSION_CODES.LOLLIPOP? 48: 0);
binaryKarmic
2
¡¡Excelente!! No importa que "windowSoftInputMode" esté configurado en "ajustarPan" / "ajustarResize" / "ajustePan | stateHidden" / "ajusteResize | stateHidden", o incluso sin esta opción, ¡siempre funciona! Probado en XiaoMi 8.
Zhou Hongbo
52

Perdón por la respuesta tardía, pero había creado una pequeña clase auxiliar para manejar eventos abiertos / cerrados con notificaciones a los oyentes y otras cosas útiles, puede ser que alguien lo encuentre útil:

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

Ejemplo de uso:

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

// Add listener
softKeyboardStateWatcher.addSoftKeyboardStateListener(...);
// then just handle callbacks
Artem Zinnatullin
fuente
2
La clase no es pequeña pero la implementación seguramente es :). Gracias, intentaré esto :)
Atul O Holic
1
Envíame un ping si tienes problemas :) Lo utilicé con éxito en 2 proyectos
Artem Zinnatullin
Después de probar muchos ejemplos anteriores y encontrar problemas menores, este fue el que mejor funcionó para mí en muchos dispositivos diferentes (incluido xxhdpi). Además, está en su propia clase agradable reutilizable. Lo convertí para usarlo en mono droide.
kheit
Algunos teclados, algunas veces, tienen una fila adicional de teclas personalizadas en la parte superior (por ejemplo, palabras predichas). Aparentemente, estos no son parte del teclado en sí, ya que el uso de getLastKeyboardHeightInPx()no incluye la altura de esa fila. ¿Conoces una manera de tenerlo en cuenta también?
ygesher
Esto solo funciona si está listo para comprometer el cambio de altura de diseño cuando aparece el teclado. ¿Correcto?
M. Usman Khan
34

Algunas mejoras para evitar detectar erróneamente la visibilidad del teclado virtual en dispositivos de alta densidad:

  1. El umbral de diferencia de altura debe definirse como 128 dp , no 128 píxeles .
    Consulte el documento de diseño de Google sobre Metrics and Grid , 48 dp es de tamaño cómodo para objetos táctiles y 32 dp es mínimo para botones. El teclado virtual genérico debe incluir 4 filas de botones de tecla, por lo que la altura mínima del teclado debe ser: 32 dp * 4 = 128 dp , lo que significa que el tamaño del umbral debe transferirse a píxeles multiplicando la densidad del dispositivo. Para dispositivos xxxhdpi (densidad 4), el umbral de altura del teclado virtual debe ser 128 * 4 = 512 píxeles.

  2. Diferencia de altura entre la vista raíz y su área visible:
    altura de la vista raíz - altura de la barra de estado - altura del marco visible = parte inferior de la vista raíz - parte inferior del marco visible, ya que la altura de la barra de estado es igual a la parte superior del marco visible de la vista raíz.

    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
fuente
44
Esto merece ser la respuesta aceptada. Ignorar la densidad me dio resultados muy diferentes en dispositivos con diferentes factores de forma pero tamaños de píxeles similares. ¡Gracias!
Ginger McMurray
2
Gracias ... esta es una mejor condición!
TacB0sS
1
Excelente. El método isKeyboardShown () es lo que necesitamos. Gracias
Danylo Volokh
También está funcionando en 2020. Respuesta perfecta. Intenté todo el código pero en esto funciona perfectamente.
Mitesh Jain
8

Utilicé un poco de tiempo para resolver esto ... Ejecuté algunas CastExceptions, pero descubrí que puedes reemplazar LinearLayout en layout.xml con el nombre de la clase.

Me gusta esto:

<?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 esa manera no te encontrarás con ningún problema de lanzamiento.

... y si no quieres hacer esto en cada página, te recomiendo que uses "MasterPage en Android". Vea el enlace aquí: http://jnastase.alner.net/archive/2011/01/08/ldquomaster-pagesrdquo-in-android.aspx

Janus Kamp Hansen
fuente
Tenga cuidado al pegar esto en su XML si no tiene el mismo nombre de paquete / clase. Eclipse simplemente decide congelarse y debes apagarlo. Un producto tan profesional. / s
Spencer Ruport
55
@SpencerRuport, por eso es gratis.
Cody
@DoctorOreo: obtén IntelliJ. Es gratis y no apesta.
Mark
@ Mark: unos meses después de publicar esto, probé IntelliJ. Es mucho mejor, en mi opinión, que Eclipse. Todos sus productos (en su mayor parte) creo que son excelentes. Incluso he comprado algunos.
Cody
Perdón por revivir un hilo de comentarios tan antiguo. Me alegra que lo estés usando y disfrutando. Me encanta usar IntelliJ, así como AppCode para iOS y PyCharm para el trabajo de Python. ¡Salud!
Mark
5

La idea es que, si necesita ocultar su teclado y verificar el estado de entrada suave al mismo tiempo, use la siguiente solución:

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

Este método devuelve verdadero si el teclado se mostró antes de ocultarse.

George Maisuradze
fuente
Este es el que funciona sin usar altura y todo ... Gracias ... me
ahorraste
4

Descubrí que una combinación del método de @ Reuben_Scratton junto con el método de @ Yogesh parece funcionar mejor. Combinar sus métodos produciría algo como esto:

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
fuente
siempre Configuration.KEYBOARDHIDDEN_NO.
fantouch
4

Puede observar la ocultación del teclado virtual mediante el uso de decorView de la actividad.

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
fuente
4

En lugar de asumir la diferencia de codificación, hice algo como esto, ya que no tenía opciones de menú en mi aplicación.

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
fuente
No funciona para Android: windowSoftInputMode = "ajustarPan". Quería que mi pantalla no se encogiera después de que aparezca el teclado virtual. ¿Puedes decirme alguna solución para que funcione incluso para el ajuste
Shirish Herwade
4

También hay una solución con inserciones del sistema, pero solo funciona con API >= 21( Android L). Digamos que tiene BottomNavigationView, que es hijo de LinearLayouty necesita ocultarlo cuando se muestra el teclado:

> LinearLayout
  > ContentView
  > BottomNavigationView

Todo lo que necesita hacer es extender LinearLayoutde tal manera:

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

La idea es que cuando se muestra el teclado, las inserciones del sistema se cambian con un .bottomvalor bastante grande .

nikis
fuente
4

Hay un método oculto que puede ayudar para esto InputMethodManager.getInputMethodWindowVisibleHeight,. Pero no sé por qué está oculto.

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
fuente
Si alguien necesita esto, también funciona en Xamarin, el nombre del método es exactamente el mismo y debe accederse de la misma manera, a través de la propiedad Class en InputMethodManager.
Konstantin Severy
Tenga cuidado al usar esto, es una API no compatible (está oculta por alguna razón) y para empezar no funciona en KitKat.
Daniele Ricci
3

Ninguna de estas soluciones funcionará para Lollipop tal como está. En Lollipop activityRootView.getRootView().getHeight()incluye la altura de la barra de botones, mientras que medir la vista no. He adaptado la mejor / más simple solución anterior para trabajar con 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
fuente
¿Por qué una solución similar de stackoverflow.com/a/18992807/2914140 no funcionó para usted y cómo difiere su solución?
CoolMind
3

Acabo de encontrar un error al usar la mayoría de las soluciones anteriores que sugieren agregar un número fijo.

S4 tiene una alta resolución, lo que resultó en que la altura de la barra de navegación sea de 100 px, por lo que mi aplicación piensa que el teclado está abierto todo el tiempo.

Por lo tanto, con el lanzamiento de todos los nuevos teléfonos de alta resolución, creo que usar un valor codificado no es una buena idea a largo plazo.

Un mejor enfoque que encontré después de algunas pruebas en varias pantallas y dispositivos fue utilizar el porcentaje. Obtenga la diferencia entre decorView y el contenido de su aplicación y luego verifique cuál es el porcentaje de esa diferencia. Según las estadísticas que obtuve, la mayoría de la barra de navegación (independientemente del tamaño, la resolución, etc.) ocupará entre el 3% y el 5% de la pantalla. Donde, como si el teclado estuviera abierto, ocupaba entre 47% y 55% de la pantalla.

Como conclusión, mi solución fue verificar si la diferencia es más del 10%, entonces supongo que es un teclado abierto.

N Jay
fuente
3

Utilicé una ligera variante de la respuesta de Reuban, que resultó ser más útil en ciertas circunstancias, especialmente con dispositivos de alta resolución.

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
fuente
¿qué es esto R.id.activityRoot
ranjith
2
en lugar de crear y usar R.id.activityRoot, simplemente puede usar android.R.id.contentcuál es exactamente lo que necesita.
Marcin Orlowski
3

Ha sido para siempre en términos de la computadora, ¡pero esta pregunta sigue siendo increíblemente relevante! Así que he tomado las respuestas anteriores y las he combinado y refinado un poco ...

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

Esto funciona para mi.

Roselyn Soffer
fuente
3

Prueba esto:

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
fuente
2

Mi respuesta es básicamente la misma que la respuesta de Kachi, pero la envolví en una buena clase auxiliar para limpiar la forma en que se usa en mi aplicación.

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

Puede usar esto para detectar cambios en el teclado en cualquier lugar de la aplicación como este:

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

Nota: solo use una de las llamadas de "registro". Todos funcionan igual y solo están ahí por conveniencia

billylindeman
fuente
2

puedes probar esto, funciona muy bien para mí:

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

if (imm.isAcceptingText()) {
    //Software Keyboard was shown..
} else {
    //Software Keyboard was not shown..
}
IRvanFauziE
fuente
1
No use esto, devuelve verdadero si el teclado estaba oculto usando la parte posterior pero la vista tiene foco, al menos en Marshmallow
Maragues
2
siempre responde que es visible
jose920405
2

Estaba teniendo dificultades para mantener el estado del teclado al cambiar la orientación de los fragmentos dentro de un visor. No estoy seguro de por qué, pero parece ser inestable y actúa de manera diferente a una Actividad estándar.

Para mantener el estado del teclado en este caso, primero debe agregar android:windowSoftInputMode = "stateUnchanged"a su AndroidManifest.xml. Sin embargo, puede notar que esto en realidad no resuelve todo el problema: el teclado no se abrió para mí si se abrió antes del cambio de orientación. En todos los demás casos, el comportamiento parecía ser correcto.

Entonces, necesitamos implementar una de las soluciones mencionadas aquí. El más limpio que encontré fue el de George Maisuradze: use la devolución de llamada booleana de hideSoftInputFromWindow:

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

Almacené este valor en el onSaveInstanceStatemétodo de mi Fragmento y lo recuperé onCreate. Luego, mostré por la fuerza el teclado onCreateViewsi tenía un valor de true(devuelve verdadero si el teclado es visible antes de ocultarlo realmente antes de la destrucción del Fragmento).

Punto cuántico
fuente
1

Aquí está mi solución, y funciona. En lugar de buscar el tamaño de píxel, simplemente verifique que la altura de la vista de contenido haya cambiado o no:

// 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();
}
Hombre malo
fuente
inputMethodManager.isActive () siempre me devuelve el valor verdadero, independientemente de si el teclado está activo o no
EionRobb
1

No hagas ningún código duro. La mejor manera es que tiene que cambiar el tamaño de sus vistas mientras se enfoca en EditText con KeyBord Show. Puede hacer esto agregando la propiedad de cambio de tamaño de la actividad en el archivo de Manifiesto usando el siguiente código.

android:windowSoftInputMode="adjustResize"

Rahul Mandaliya
fuente
1

Hay un método directo para descubrir esto. Y no requiere ningún cambio de diseño.
Por lo tanto, también funciona en modo inmersivo a pantalla completa.

El truco es que intentas ocultar o mostrar el teclado virtual y capturar el resultado de ese intento.
Sin pánico, esto realmente no muestra u oculta el teclado. Solo pedimos el estado.

Para mantenerse actualizado, simplemente puede repetir la operación, por ejemplo, cada 200 milisegundos, utilizando un controlador.

Encuentra una implementación aquí: https://stackoverflow.com/a/27567074/2525452

fies
fuente
1

Creo que este método te ayudará a descubrir si keybord es visible o no.

 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
fuente
siempre responde que es visible
jose920405
0

La nueva respuesta de Reuben Scratton (calcular la diferencia de altura int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();) no funcionará en la actividad si configura el modo de barra de estado translúcido.

Si usa la barra de estado translúcida, activityRootView.getHeight()nunca cambiará el clima si el teclado virtual está visible. siempre devolverá la altura de la actividad y la barra de estado.

Por ejemplo, Nexus 4, Android 5.0.1, configurado android:windowTranslucentStatusen verdadero, devolverá 1184 para siempre, incluso si el iMe ha abierto. Si lo establece android:windowTranslucentStatusen falso, devolverá la Altura correctamente, si estoy invisible, devolverá 1134 (no incluye la barra de estado) 。Cierre la imagen, tal vez devolverá 5xx (depende de la altura de la imagen)

No sé si el clima es un error, lo he probado en 4.4.4 y 5.0.1, el resultado es el mismo.

Entonces, hasta ahora, la segunda respuesta más acordada, la solución de Kachi será la forma más segura de calmar la altura del tiempo. Aquí hay una copia:

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
fuente
0

Un método que no necesita un LayoutListener

En mi caso, me gustaría guardar el estado del teclado antes de reemplazar mi Fragmento. Llamo al método hideSoftInputFromWindow from onSaveInstanceState, que cierra el teclado y me devuelve si el teclado estaba visible o no.

Este método es sencillo pero puede cambiar el estado de su teclado.

Gordak
fuente
0

Sé que esta es una publicación antigua, pero creo que este es el enfoque más simple que conozco y mi dispositivo de prueba es Nexus 5. No lo he probado en otros dispositivos. Espero que otros compartan su enfoque si encuentran que mi código no es bueno :)

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 devuelve boolean.

Gracias,

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

La función anterior es la que uso para verificar si un teclado está visible. Si es así, entonces lo cierro.

A continuación se muestran los dos métodos requeridos.

Primero, defina la altura de la ventana viable en 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;
//

} 

Luego, agregue un método booleano que obtenga la altura de la ventana en esa instancia. Si no coincide con el original (suponiendo que no lo esté cambiando en el camino ...), entonces, el teclado está abierto.

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
fuente
0

Sé cuán exacto puede determinar si el teclado está oculto o no.

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

Esto funciona para tabletas. Cuando la barra de navegación se muestra horizontalmente.

Valentin Baryshev
fuente