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?

deSelby
fonte

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

Jay
fonte
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);
}
olivier_sdg
fonte
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!

Steelight
fonte
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);
    }
}
O Melhor Artista
fonte
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!!
      }
Leo Droidcoder
fonte
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;}
farhad.kargaran
fonte
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);
    }
Rubin Yoo
fonte
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
    }
}
bmjohns
fonte
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>
};
Steven Evers
fonte
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
        }
Bali
fonte