Detectar quando o usuário dispensou o teclado virtual


114

Eu tenho um widget EditText em minha visualização. Quando o usuário seleciona o widget EditText, eu exibo algumas instruções e o teclado virtual aparece.

Eu uso um OnEditorActionListener para detectar quando o usuário completou a entrada de texto e eu dispenso o teclado, oculto as instruções e executo alguma ação.

Meu problema é quando o usuário dispensa o teclado pressionando a tecla BACK. O sistema operacional dispensa o teclado, mas minhas instruções (que preciso ocultar) ainda estão visíveis.

Eu tentei substituir OnKeyDown, mas ele não parece ser chamado quando o botão BACK é usado para dispensar o teclado.

Eu tentei definir um OnKeyListener no widget EditText, mas ele também não parece ser chamado.

Como posso detectar quando o teclado virtual está sendo dispensado?

Respostas:


160

Eu conheço uma maneira de fazer isso. Subclasse o 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);
}

Aqui está um link sobre como usar suas visualizações personalizadas (para quando você criar uma subclasse de EditText): http://developer.android.com/guide/topics/ui/custom-components.html


2
Estou recebendo relatórios de usuários de Android com teclados de hardware que, de alguma forma, interferem no pressionamento de teclas. Não tenho nenhuma informação adicional neste momento.
— esilver

Tenho procurado várias soluções, esta é de longe a melhor!
— Friesgaard

10
Espere, espere, acabei de olhar para isso pela terceira vez - a superchamada não deveria ser para onKeyPreIme? Ou existe uma razão particular para que não seja assim?
— Erhannis

Parece útil, exceto onde o EditText não pode ser uma subclasse (por exemplo, em um SearchView). Este é um problema ao tentar ocultar o SearchView se estiver vazio quando o teclado for dispensado. Eu tenho que me perguntar por que o pessoal do Android não fornece apenas algumas APIs OSK interessantes para esse tipo de coisa.
— tbm

2
@tbm Para obter um efeito semelhante em SearchView, consulte stackoverflow.com/questions/9629313/…
— Cheok Yan Cheng

124

Jay, sua solução é boa! obrigado :)

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

Alguma razão particular que desejamos detectar KeyEvent.ACTION_UPtambém?
— Cheok Yan Cheng de

2
@CheokYanCheng é porque a ação do usuário normalmente deve ter efeito ao liberar o botão, e não ao começar a pressioná-lo.
— Jayeffkay

3
Certifique-se de estender android.support.v7.widget.AppCompatEditTextpara tingir.
— Sanvywell

Estender: AppCompatEditTextpara androidx
— COYG

Ótimo! Eu sugeriria apenas uma melhoria apenas para generalizar sua solução. Eu passaria os argumentos de onKeyPreIme "no estado em que se encontram" para o ouvinte, dessa forma, você pode implementar sua lógica de diferentes maneiras, quando necessário.
— marcRDZ

17

Fiz uma pequena mudança na solução de Jay chamando 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);
    }
};

Solução maravilhosa, Jay, +1!


14

Aqui está o meu EditText personalizado para detectar se o teclado está sendo exibido ou não

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

8

Agora é 2019 ...
Então, criei uma solução mais bacana com o Kotlin

1. Crie uma função de extensão:

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. Onde o ouvinte de alternância está:

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. Use-o em qualquer atividade tão simples como esta :

addKeyboardToggleListener {shown ->
          // hurray! Now you know when the keyboard is shown and hidden!!
      }

Obrigado pela solução Kotlin. Porém, quando o implementei, notei que ele dispara o ouvinte várias vezes para uma mudança de teclado e também na inicialização. Tive que armazenar o estado aberto / não aberto e apenas invocar os ouvintes quando o valor fosse realmente diferente.
— RandomEngy

@RandomEngy corrigiu isso no KeyboardToggleListener. Obrigado por notar
— Leo Droidcoder

4

Basta criar uma classe que estenda Edittext e usar esse edittext em seu código. Você deve apenas substituir o seguinte método no 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;}

Existe uma maneira de detectar quando o teclado abre?
— Powder366

3

Aqui está uma solução com o ouvinte principal. Não tenho ideia de por que isso funciona, mas OnKeyListener funciona se você simplesmente substituir onKeyPreIme em seu 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);
    }

3

Usando a resposta de @olivier_sdg, mas convertido para 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
    }
}

2

Para quem quer fazer o mesmo no Xamarin, traduzi algumas das principais respostas, pois é um pouco diferente. Eu criei uma essência aqui, mas, resumindo, você cria um EditText personalizado e sobrescreve OnKeyPreImeassim:

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

... e então na vista ...

editText = FindViewById<CustomEditText>(Resource.Id.MyEditText);
editText.BackPressed += (s, e) => 
{
    // <insert code here>
};

Embora seja apenas um exemplo simples, eu recomendaria não usar métodos anônimos em manipuladores de eventos, pois eles criam vazamentos de memória, e muitas pessoas usam os exemplos encontrados aqui e os executam sem perceber. Fonte: docs.microsoft.com/en-us/xamarin/cross-platform/deploy-test/…
— Jared

0

hideSoftInputFromWindow retorna verdadeiro quando o teclado fecha, usa seu valor para detectar o fechamento do teclado no 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
        }
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.