Detectar cuando el usuario ha descartado el teclado virtual

114

Tengo un widget EditText en mi vista. Cuando el usuario selecciona el widget EditText, muestro algunas instrucciones y aparece el teclado virtual.

Utilizo un OnEditorActionListener para detectar cuando el usuario ha completado la entrada de texto y descarto el teclado, oculto las instrucciones y realizo alguna acción.

Mi problema es cuando el usuario cierra el teclado presionando la tecla ATRÁS. El sistema operativo descarta el teclado, pero mis instrucciones (que necesito ocultar) aún están visibles.

Intenté anular OnKeyDown, pero parece que no se llama cuando se usa el botón ATRÁS para cerrar el teclado.

Intenté configurar un OnKeyListener en el widget EditText, pero tampoco parece que lo llamen.

¿Cómo puedo detectar cuándo se cierra el teclado virtual?

deSelby
fuente

Respuestas:

160

Conozco una forma de hacer esto. Subclase EditText e implemente:

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
  if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
    // Do your thing.
    return true;  // So it is not propagated.
  }
  return super.dispatchKeyEvent(event);
}

Aquí hay un enlace sobre cómo usar sus vistas personalizadas (para cuando subclase EditText): http://developer.android.com/guide/topics/ui/custom-components.html

Arrendajo
fuente
2
Recibo informes de usuarios de Android con teclados de hardware de que hacer esto de alguna manera interfiere con las pulsaciones de teclas. No tengo ninguna información adicional en este momento.
esilver
He estado buscando varias soluciones, ¡esta es, con mucho, la mejor!
Friesgaard
10
Espera espera espera, acabo de ver esto por tercera vez, ¿no debería ser la súper llamada a onKeyPreIme? ¿O hay una razón particular para que no sea así?
Erhannis
Parece útil, excepto donde EditText no puede ser subclasificado (por ejemplo, en un SearchView). Este es un problema al intentar ocultar SearchView si está vacío cuando se cierra el teclado. Me pregunto por qué la gente de Android no solo proporciona algunas API de OSK agradables para este tipo de cosas.
tbm
2
@tbm Para lograr un efecto similar en SearchView, consulte stackoverflow.com/questions/9629313/…
Cheok Yan Cheng
124

Jay, ¡tu solución es buena! Gracias :)

public class EditTextBackEvent extends EditText {

    private EditTextImeBackListener mOnImeBack;

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

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

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

    @Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && 
            event.getAction() == KeyEvent.ACTION_UP) {
            if (mOnImeBack != null) 
                mOnImeBack.onImeBack(this, this.getText().toString());
        }
        return super.dispatchKeyEvent(event);
    }

    public void setOnEditTextImeBackListener(EditTextImeBackListener listener) {
        mOnImeBack = listener;
    }

}

public interface EditTextImeBackListener {
    public abstract void onImeBack(EditTextBackEvent ctrl, String text);
}
olivier_sdg
fuente
¿Alguna razón en particular que queramos detectar KeyEvent.ACTION_UPtambién?
Cheok Yan Cheng
2
@CheokYanCheng se debe a que la acción del usuario normalmente debería tener efecto al soltar el botón y no al comenzar a presionarlo.
jayeffkay
3
Asegúrese de extender android.support.v7.widget.AppCompatEditTextpara teñir.
Sanvywell
Ampliar: AppCompatEditTextpara androidx
COYG
¡Excelente! Sugeriría solo una mejora solo para generar su solución. Pasaría los argumentos de onKeyPreIme "tal cual" al oyente, de esta manera puede implementar su lógica de diferentes maneras donde lo necesite.
marcRDZ
17

Hice un pequeño cambio en la solución de Jay llamando a super.onKeyPreIme ():

_e = new EditText(inflater.getContext()) {
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK){
            cancelTextInput();
        }
        return super.onKeyPreIme(keyCode, event);
    }
};

¡Maravillosa solución, Jay, +1!

Steelight
fuente
14

Aquí está mi EditText personalizado para detectar si el teclado se muestra o no

/**
 * Created by TheFinestArtist on 9/24/15.
 */
public class KeyboardEditText extends EditText {

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

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

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

    @Override
    protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(focused, direction, previouslyFocusedRect);
        if (listener != null)
            listener.onStateChanged(this, true);
    }

    @Override
    public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
        if (event.getKeyCode() == KeyEvent.KEYCODE_BACK
                && event.getAction() == KeyEvent.ACTION_UP) {
            if (listener != null)
                listener.onStateChanged(this, false);
        }
        return super.onKeyPreIme(keyCode, event);
    }

    /**
     * Keyboard Listener
     */
    KeyboardListener listener;

    public void setOnKeyboardListener(KeyboardListener listener) {
        this.listener = listener;
    }

    public interface KeyboardListener {
        void onStateChanged(KeyboardEditText keyboardEditText, boolean showing);
    }
}
El mejor artista
fuente
8

Es 2019 ahora ...
Así que creé una solución más ordenada con Kotlin

1.Crear una función de extensión:

fun Activity.addKeyboardToggleListener(onKeyboardToggleAction: (shown: Boolean) -> Unit): KeyboardToggleListener? {
    val root = findViewById<View>(android.R.id.content)
    val listener = KeyboardToggleListener(root, onKeyboardToggleAction)
    return root?.viewTreeObserver?.run {
        addOnGlobalLayoutListener(listener)
        listener
    }
}

2.Dónde está el oyente de alternancia:

open class KeyboardToggleListener(
        private val root: View?,
        private val onKeyboardToggleAction: (shown: Boolean) -> Unit
) : ViewTreeObserver.OnGlobalLayoutListener {
    private var shown = false
    override fun onGlobalLayout() {
        root?.run {
            val heightDiff = rootView.height - height
            val keyboardShown = heightDiff > dpToPx(200f)
            if (shown != keyboardShown) {
                onKeyboardToggleAction.invoke(keyboardShown)
                shown = keyboardShown
            }
        }
    }
}

fun View.dpToPx(dp: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, resources.displayMetrics).roundToInt()

3. Úselo en cualquier actividad tan simple como esta :

addKeyboardToggleListener {shown ->
          // hurray! Now you know when the keyboard is shown and hidden!!
      }
Leo Droidcoder
fuente
Gracias por la solución de Kotlin. Aunque cuando lo implementé, noté que activa el oyente varias veces para un cambio de teclado y también al inicio. Tuve que almacenar el estado abierto / no abierto y solo invocar a los oyentes cuando el valor era realmente diferente.
RandomEngy
@RandomEngy lo arregló en KeyboardToggleListener. Gracias por
darte
4

Simplemente cree una clase que amplíe Edittext y use ese edittext en su código, simplemente debe anular el siguiente método en el edittext personalizado:

@Override
 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
 if (keyCode == KeyEvent.KEYCODE_BACK) {

    //Here it catch all back keys
    //Now you can do what you want.

} else if (keyCode == KeyEvent.KEYCODE_MENU) {
    // Eat the event
    return true;
}
return false;}
farhad.kargaran
fuente
¿Hay alguna forma de detectar cuándo se abre el teclado?
powder366
3

Aquí hay una solución con el oyente clave. No tengo idea de por qué funciona esto, pero OnKeyListener funciona si simplemente anula onKeyPreIme en su EditText personalizado.

SomeClass.java

customEditText.setOnKeyListener((v, keyCode, event) -> {
            if(event.getAction() == KeyEvent.ACTION_DOWN) {
                switch (keyCode) {
                    case KeyEvent.KEYCODE_BACK:
                        getPresenter().onBackPressed();
                        break;
                }
            }
            return false;
        }); 

CustomEditText.java

@Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        return super.dispatchKeyEvent(event);
    }
Rubin Yoo
fuente
3

Usando la respuesta de @ olivier_sdg, pero convertido a Kotlin:

class KeyboardEditText : AppCompatEditText {

    var listener: Listener? = null
  
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
  
    override fun onKeyPreIme(keyCode: Int, event: KeyEvent): Boolean {
        if (event.keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
            listener?.onImeBack(this)
        }
        return super.dispatchKeyEvent(event)
    }
  
    interface Listener {
        fun onImeBack(editText: KeyboardEditText)
    }

}

Uso:

keyboardEditText.listener = object : KeyboardEditText.Listener {
    override fun onImeBack(editText: KeyboardEditText) {
        //Back detected
    }
}
bmjohns
fuente
2

Para cualquiera que busque hacer lo mismo en Xamarin, he traducido algunas de las respuestas principales, ya que es un poco diferente. Creé una esencia aquí, pero resumiendo, creas un EditText personalizado y lo anulas OnKeyPreImeasí:

public class CustomEditText : EditText
{
    public event EventHandler BackPressed;

    // ...

    public override bool OnKeyPreIme([GeneratedEnum] Keycode keyCode, KeyEvent e)
    {
        if (e.KeyCode == Keycode.Back && e.Action == KeyEventActions.Up)
        {
            BackPressed?.Invoke(this, new EventArgs());
        }

        return base.OnKeyPreIme(keyCode, e);
    }
}

... y luego en la vista ...

editText = FindViewById<CustomEditText>(Resource.Id.MyEditText);
editText.BackPressed += (s, e) => 
{
    // <insert code here>
};
Steven Evers
fuente
Si bien es solo un ejemplo simple, recomendaría no usar métodos anónimos en los controladores de eventos, ya que estos crean pérdidas de memoria, y muchas personas usan los ejemplos que se encuentran aquí y los ejecutan sin darse cuenta de esto. Fuente: docs.microsoft.com/en-us/xamarin/cross-platform/deploy-test/…
Jared
0

hideSoftInputFromWindow devuelve verdadero cuando el teclado se cierra, use su valor para detectar el cierre del teclado en Android

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

        if (imm.hideSoftInputFromWindow(findFocus().getWindowToken(),
                InputMethodManager.HIDE_NOT_ALWAYS)) {
            //keyboard is closed now do what you need here
        }
Bali
fuente