O Android esqueceu do Listener de teclado?

Android SDK é excelente e sabemos que a turma da Google fez um ótimo trabalho, mas volta e meia encontramos algum recurso que por uma razão ou outra foi deixado de lado ou às vezes até mal desenvolvido. Um desses probleminhas, que dependendo do seu objetivo pode ser uma grande dor de cabeça, é a ausência de Listener para detectar os eventos de teclado , como a Entrada e Saída do teclado do SoftKeyBoard.

Este problema é uma questão recorrente no stackoverflow. E após horas de pesquisa não pude encontrar nenhuma resposta definitiva. Entre vários métodos que testei, o mais eficiente foi um hack que utiliza a diferença da medida da janela para deduzir a presença do teclado. 

Vale ressaltar que a solução apresentada aqui não é canônica ou definitiva, mas  um hack com resultados bastante confiáveis.

 

Vamos criar nossos eventos de teclado em Activity

A forma mais produtiva de utilizar este snippet é incluir o código em uma extensão abstrata de uma Atividade, e utilizar a extensão nas Atividades onde há a necessidade de detectar a presença do teclado ou alterações no layout.

// Dispara quando o teclado aparece na tela
protected void onKeyBoardOn() {
    Log.d(TAG, "onKeyBoardOn()");
}
// Dispara quando o teclado desaparece
protected void onKeyBoardOff() {
    Log.d(TAG, "onKeyBoardOff()");
}
// Dispara quando ocorrem alterações na altura da janela
protected void onRootLayoutChanged(int heightDiff) {
    Log.d(TAG, "onRootLayoutChanged(heightDiff[" + heightDiff + "])");
}

Registramos um controle da presença do teclado para controle e futuras checagens.

private Boolean mIsKeyboardOn;
/**
 * Controla a presença do teclado
 * - pode retornar null se o observador de teclado não foi iniciado
 */
protected Boolean isKeyboardOn() {
    Log.d(TAG, "isKeyboardOn()");
    return mIsKeyboardOn;
}

Utilizamos um ViewTreeObserver para observar as mudanças na árvore de layout

Como o nome diz, o ViewTreeObserver cria listeners que são notificados com qualquer mudança ocorrida na árvore de layout registrada.

protected View mActivityRootView;
protected ViewTreeObserver.OnGlobalLayoutListener mLayoutListener;

/**
 * Criar um observador global para a árvore de layout
 */
private ViewTreeObserver.OnGlobalLayoutListener createLayoutListener() {
    // Altura limite que irá disparar eventos, alertando de uma alteração
    // que provavelmente corresponde a inserção do teclado na tela
    final int KEYBOARD_GREATER_THAN = 125;
    return new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            // Utilizamos um Rect para medir o espaço disponível
            // na janela onde nosso root layout está inserido
            Rect rect = new Rect();
            mActivityRootView.getWindowVisibleDisplayFrame(rect);
            // calculamos a diferença entra a altura da janela
            // e a altura atual da raiz do layout
            int heightDiff = mActivityRootView.getHeight() - (rect.bottom - rect.top);
            // Caso alterações ocorram no layout, informar
            if (heightDiff>0) onRootLayoutChanged(heightDiff);
            
            // Caso a diferença da altura for maior que a estabelecida
            // registrar como presença do teclado
            if (heightDiff > KEYBOARD_GREATER_THAN) { 
                onKeyBoardOn();
                mIsKeyboardOn = true;
            } else {
                if (mIsKeyboardOn) {
                    onKeyBoardOff();
                    mIsKeyboardOn = false;
                }
            }
        }
    };
}

 

Criamos métodos para iniciar e parar a observação do layout e consequentemente os eventos de teclado on e off.

/**
 * Inicia a deteção de mudanças na altura do layout
 * - Registra um view raiz
 * - Cria e inicia um ViewTreeObserver
 * - Registra eventos para o layout raiz
 *      {@link #onKeyBoardOn()}
 *      {@link #onKeyBoardOff()}
 *      {@link #onRootLayoutChanged(int)}
 * @param activityRootView  Layout raiz a ser monitorado
 */
protected void startDetectRootViewChanges(final View activityRootView) {
    Log.d(TAG, "startDetectRootViewChanges()");
    mActivityRootView = activityRootView;
    mIsKeyboardOn = false;

    mLayoutListener = createLayoutListener();
    mActivityRootView.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener);
}

/**
 * Remove o observador da raiz do layout
 */
protected void stopDetectRootViewChanges() {
    Log.d(TAG, "stopDetectRootViewChanges()");
    if ( mActivityRootView != null )
        mActivityRootView.getViewTreeObserver().removeOnGlobalLayoutListener(mLayoutListener);
}

O observador deve ser chamado nos primeiros momentos do ciclo de vida da atividade. Também é importante lembrar de remover o observador na destruição da atividade.

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   // Registramos o layout raiz
   // Deve ser o seu primeiro layout da atividade
   CoordinatorLayout layoutRaiz = (CoordinatorLayout) findViewById(R.id.layout_raiz);
   startDetectRootViewChanges(layoutRaiz);
}
@Override
protected void onDestroy(){
   // Remove o observador de layout
   stopDetectRootViewChanges(); 
}

Como havia dito, a forma mais produtiva de utilizar este snippet é registrar em um abstrato de Atividade, assim você precisará somente iniciar o startDetectRootViewChanges  em onCreate e subscrever os métodos onKeyBoardOn() e onKeyBoardOff() .

O código fonte desta Atividade abstrata você encontra aqui.


Also published on Medium.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *