¿Cómo capturar el evento "teclado virtual mostrar / ocultar" en Android?

Respuestas:

69

Nota

Esta solución no funcionará para teclados suaves y onConfigurationChangedno se llamará para teclados suaves (virtuales).


Tienes que manejar los cambios de configuración tú mismo.

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

Muestra:

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


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

Luego, simplemente cambie la visibilidad de algunas vistas, actualice un campo y cambie su archivo de diseño.

Pedro Loureiro
fuente
44
@shiami try newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO~ Chris
cimnine
3
Esto solo funciona si ha registrado la actividad para escuchar los cambios de configuración que desea en el AndroidManifest.
Raúl Agrait
66
por favor actualice su respuesta y diga que no funciona para el teclado virtual. Perdí mi medio día probando tu código. Y luego vi estos comentarios.
Shirish Herwade
17
Esto no funciona para teclados "virtuales", que era la pregunta original.
brummfondel
18
Bueno, la pregunta era sobre el TECLADO SUAVE, ¿por qué la respuesta aceptada sobre un teclado de hardware? -1!
Denys Vitali
56

Esta puede no ser la solución más efectiva. Pero esto funcionó para mí cada vez ... Llamo a esta función donde necesito escuchar el softKeyboard.

boolean isOpened = false;

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

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

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

Nota: Este enfoque causará problemas si el usuario usa un teclado flotante.

amalBit
fuente
1
addOnGlobalLayoutListener?
coolcool1994
77
Esto huele a una pérdida de memoria. Está agregando un oyente a un objeto global, que se aferrará a usted y nunca lo dejará ir.
flexicious.com
99
Este tampoco funcionará para Actividades configuradas con android:windowSoftInputMode="adjustPan", o adjustResizecon una ventana de pantalla completa, ya que el diseño nunca cambia de tamaño.
Ionoclast Brigham
1
Esto solo funciona con AdjustResize. Para ajustarPan, el heightDiff nunca cambia.
alexhilton
2
¿Por qué estás comparando un booleano?
Xerus
37

Si desea manejar mostrar / ocultar la ventana del teclado IMM (virtual) desde su Actividad, deberá subclasificar su diseño y anular el método onMesure (para que pueda determinar el ancho medido y la altura medida de su diseño). Después de eso, establezca el diseño subclasificado como vista principal para su Actividad mediante setContentView (). Ahora podrá manejar los eventos de ventana mostrar / ocultar IMM. Si esto suena complicado, en realidad no es así. Aquí está el código:

main.xml

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

Ahora dentro de su Actividad, declare la subclase para su diseño (main.xml)

    public class MainSearchLayout extends LinearLayout {

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

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

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

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

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

Puede ver en el código que inflamos el diseño de nuestra Actividad en el constructor de subclase

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

Y ahora solo configure la vista de contenido del diseño subclasificado para nuestra Actividad.

public class MainActivity extends Activity {

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

        MainSearchLayout searchLayout = new MainSearchLayout(this, null);

        setContentView(searchLayout);
    }

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

}
Nebojsa Tomcic
fuente
3
Necesito investigar más a fondo, pero tengo mis dudas sobre si esto funcionaría en mi caso para un diálogo pequeño en un dispositivo de pantalla grande para el cual las medidas de diseño no se verían afectadas por la presencia de un teclado.
PJL
44
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
Esto no funciona, siempre va a la parte else aquí si (actualHeight> altura propuesta) {// Se muestra el teclado} else {// El teclado está oculto}
Aamirkhan
También puede usar una Vista personalizada con esa misma idea, sigue un ejemplo gist.github.com/juliomarcos/8ca307cd7eca607c8547
Julio Rodrigues
1
No funcionará para Actividades configuradas con android:windowSoftInputMode="adjustPan", o adjustResizecon una ventana de pantalla completa, ya que el diseño nunca cambia de tamaño.
Ionoclast Brigham
35

Hice de esta manera:

Agregar OnKeyboardVisibilityListenerinterfaz.

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

HomeActivity.java :

public class HomeActivity extends Activity implements OnKeyboardVisibilityListener {

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

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

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

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

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


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

Espero que esto te ayude.

Hiren Patel
fuente
2
Gracias Hiren. Esta es la solución perfecta +1
Harin Kaklotar
1
Gracias, trabajó para mi! Si solo desea ajustar su RecyclerView, vea la solución aquí: stackoverflow.com/a/43204258/373106
David Papirov el
1
Implementación reutilizable perfecta, trabajada en Actividad o Fragmento, gracias
Pelanes
1
muy lindo ty.
ZaoTaoBao
@DavidPapirov, pegó un enlace a un RecyclerView, pero no se menciona aquí.
CoolMind
22

Basado en el Código de Nebojsa Tomcic, he desarrollado la siguiente Subclase RelativeLayout:

import java.util.ArrayList;

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

public class KeyboardDetectorRelativeLayout extends RelativeLayout {

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

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

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

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

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

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

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

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

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

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

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

}

Esto funciona bastante bien ... Marque, que esta solución simplemente funcionará cuando el Modo de entrada suave de su Actividad esté configurado en "WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE"

Stefan
fuente
3
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
1
Este tampoco funcionará para Actividades configuradas con android:windowSoftInputMode="adjustPan", o adjustResizecon una ventana de pantalla completa, ya que el diseño nunca cambia de tamaño.
Ionoclast Brigham
se dispara varias veces.
zionpi
22

Al igual que la respuesta de @ amalBit, registre un oyente en el diseño global y calcule la diferencia de la parte inferior visible de dectorView y su parte inferior propuesta, si la diferencia es mayor que algún valor (altura de IME adivinada), creemos que IME está arriba:

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

private boolean keyboardShown(View rootView) {

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

el umbral de altura 100 es la altura mínima adivinada de IME.

Esto funciona tanto para ajustarPan como para ajustarResize.

alexhilton
fuente
2
¡Estoy a punto de tirar de mi cabello! Me salvaste el pelo;)
Vijay Singh Chouhan
1
Es la única buena respuesta aquí, funciona en un teclado suave perfecto, gracias
Z3nk
12

La solución de Nebojsa casi me funcionó. Cuando hice clic dentro de un EditText de varias líneas, sabía que se mostraba el teclado, pero cuando comencé a escribir dentro de EditText, la altura real y la altura propuesta seguían siendo las mismas, por lo que no sabía que todavía se mostraba el teclado. Hice una ligera modificación para almacenar la altura máxima y funciona bien. Aquí está la subclase revisada:

public class CheckinLayout extends RelativeLayout {

    private int largestHeight;

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

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

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

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

No estoy seguro si alguien publica esto. ¡Encontré esta solución simple de usar! . La clase SoftKeyboard está en gist.github.com . Pero mientras la devolución de llamada del evento emergente / oculto del teclado necesitamos un controlador para hacer correctamente las cosas en la interfaz de usuario:

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

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

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

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

    }   
});
Robert
fuente
aquí está el Git para obtener SoftkeyBoard " gist.github.com/felHR85/… "
douarbou
9

Resuelvo esto anulando onKeyPreIme (int keyCode, KeyEvent event) en mi EditText personalizado.

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
        //keyboard will be hidden
    }
}
qbait
fuente
¿Cómo usarlo en Fragment o Activity? @Qbait
Maulik Dodia
No funciona, solo se puede llamar cuando salgo de la página en mi caso.
DysaniazzZ
Este es un método de EditText, mira esta respuesta: stackoverflow.com/a/5993196/2093236
Dmide
4

Tengo una especie de truco para hacer esto. Aunque no parece haber una forma de detectar cuándo se ha mostrado u ocultado el teclado virtual, de hecho puede detectar cuándo está a punto de mostrarse u ocultarse configurando un botón OnFocusChangeListeneren el EditTextque está escuchando.

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

NOTA: Una cosa a tener en cuenta con este truco es que esta devolución de llamada se dispara inmediatamente cuando EditTextgana o pierde el foco. Esto realmente disparará justo antes de que se muestre u oculte el teclado virtual. La mejor manera que he encontrado para hacer algo después de que el teclado muestra u oculta es usar Handleray retrasar algo ~ 400 ms, así:

EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
    {
        @Override
        public void onFocusChange(View view, boolean hasFocus)
        {
            new Handler().postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        //do work here
                    }
                }, 400);
        }
    });
mentes venenosas
fuente
1
No funciona, de lo contrario. OnFocusChangeListenersolo dice si EditTexttiene foco después de que el estado cambió. Pero IMEpuede estar oculto cuando EditTexttiene foco, ¿cómo detectar este caso?
DysaniazzZ
2

He resuelto el problema en la codificación posterior de vista de texto de una sola línea.

package com.helpingdoc;

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

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

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


    }

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


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

        }

        if(proposedheight == hieght){
             // Keyboard is hidden
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
usuario2462737
fuente
2
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
Cuando la función hide / show, este método de escucha llama dos o tres veces. No sé cuál es exactamente el problema.
Jagveer Singh Rajput
2

También puede verificar el primer relleno inferior infantil de DecorView. Se establecerá en un valor distinto de cero cuando se muestre el teclado.

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

Ocultar | Mostrar eventos para el teclado se puede escuchar a través de un simple hack en OnGlobalLayoutListener:

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

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

Aquí activityRootView es la vista raíz de su Actividad.

Varun Verma
fuente
mi heightDiff es 160 al inicio y 742 con kbd, así que tuve que presentar y establecer initialHeightDiff al inicio
djdance
0

usando viewTreeObserver para obtener fácilmente el evento del teclado.

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

** layout_parent es tu vista comoedit_text.parent

Geet Thakur
fuente
-2

La respuesta de Nebojsa Tomcic no fue útil para mí. Tengo RelativeLayoutcon TextViewy AutoCompleteTextViewdentro de ella. Necesito desplazarme TextViewhacia la parte inferior cuando se muestra el teclado y cuando está oculto. Para lograr esto, anulé el onLayoutmétodo y funciona bien para mí.

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

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

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