Como capturar o evento "mostrar / ocultar teclado virtual" no Android?

Respostas:

69

Nota

Esta solução não funcionará para teclados flexíveis e onConfigurationChangednão será chamada para teclados flexíveis (virtuais).


Você precisa lidar com as alterações de configuração.

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

Amostra:

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

Em seguida, basta alterar a visibilidade de algumas visualizações, atualizar um campo e alterar seu arquivo de layout.

Pedro Loureiro
fonte
4
@shiami try newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO~ Chris
cimnine
3
Isso só funciona se você registrou a atividade para ouvir as alterações de configuração desejadas no AndroidManifest.
Raul Agrait 18/08/19
65
atualize sua resposta e diga que não funciona com o teclado virtual. Eu perdi meu meio dia tentando seu código. E então vi esses comentários.
Shirish Herwade
17
Isso não está funcionando para teclados "virtuais", que era a pergunta original.
Brunmfondel
18
Bem, a pergunta era sobre o SOFT KEYBOARD, por que a resposta aceita sobre um teclado de hardware? -1!
Denys Vitali
56

Esta pode não ser a solução mais eficaz. Mas isso funcionou para mim todas as vezes ... Eu chamo essa função sempre que preciso ouvir o 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: Essa abordagem causará problemas se o usuário usar um teclado flutuante.

amalBit
fonte
1
addOnGlobalLayoutListener?
precisa saber é o seguinte
7
Isso cheira a um vazamento de memória. Você está adicionando um ouvinte a um objeto global, que o segurará e nunca o deixará ir.
flexicious.com
9
Este também não funcionará para Atividades definidas com android:windowSoftInputMode="adjustPan"ou adjustResizecom uma janela de tela cheia, pois o layout nunca é redimensionado.
Ionoclast Brigham
1
Isso funciona apenas com o AdjustResize. Para AdjustPan, o heightDiff nunca muda.
Alexhilton # 9/15
2
por que você está comparando um booleano?
Xerus
37

Se você deseja lidar com mostrar / ocultar a janela do teclado IMM (virtual) da sua Atividade, precisará subclassificar seu layout e substituir o método onMesure (para poder determinar a largura medida e a altura medida do seu layout). Depois disso, defina o layout da subclasse como visualização principal da sua Atividade por setContentView (). Agora você poderá lidar com eventos de janela de mostrar / ocultar do IMM. Se isso parece complicado, não é realmente assim. Aqui está o 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>

Agora, dentro da sua atividade, declare a subclasse do seu layout (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);
    }
}

Você pode ver no código que inflamos o layout para nossa Activity no construtor de subclasses

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

E agora apenas defina a exibição do conteúdo do layout subclassificado para nossa Atividade.

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
fonte
3
Preciso investigar mais, mas tenho minhas dúvidas sobre se isso funcionaria no meu caso para uma pequena caixa de diálogo em um dispositivo de tela grande para o qual as medidas de layout não seriam afetadas pela presença de um teclado.
PJL
4
Não funciona no android: windowSoftInputMode = "AdjustPan". Eu queria que minha tela não ficasse reduzida depois que o teclado virtual aparecer. Pode dizer qualquer correção para que ele funciona mesmo para adjustPan
Shirish Herwade
Isto não está funcionando, ele sempre vai para a parte mais aqui if (ActualHeight> proposedheight) {// teclado é mostrado} else {// teclado está escondido}
Aamir Khan
Você também pode usar uma tela personalizada com essa mesma idéia, segue um exemplo gist.github.com/juliomarcos/8ca307cd7eca607c8547
Julio Rodrigues
1
Não funcionará para Atividades definidas com android:windowSoftInputMode="adjustPan"ou adjustResizecom uma janela de tela cheia, pois o layout nunca é redimensionado.
Ionoclast Brigham
35

Eu fiz assim:

Adicione OnKeyboardVisibilityListenerinterface.

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 isso possa ajudá-lo.

Hiren Patel
fonte
2
Obrigado Hiren. Esta é a solução perfeita +1
Harin Kaklotar
1
Obrigado, trabalhou para mim! Se você quer apenas ajustar o seu RecyclerView, consulte solução aqui: stackoverflow.com/a/43204258/373106
David Papirov
1
Implementação reutilizável perfeito, trabalhou em atividade ou fragmento, graças
Pelanes
1
muito legal ty.
ZaoTaoBao 17/04
@DavidPapirov, você colou um link para um RecyclerView, mas não o mencionou aqui.
CoolMind 18/07/2018
22

Com base no código da Nebojsa Tomcic, desenvolvi a seguinte subclasse 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();
        }
    }

}

Isso funciona muito bem ... Observe que esta solução funcionará apenas quando o Modo de entrada suave da sua atividade estiver definido como "WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE"

Stefan
fonte
3
Não funciona no android: windowSoftInputMode = "AdjustPan". Eu queria que minha tela não ficasse reduzida depois que o teclado virtual aparecer. Pode dizer qualquer correção para que ele funciona mesmo para adjustPan
Shirish Herwade
1
Este também não funcionará para Atividades definidas com android:windowSoftInputMode="adjustPan"ou adjustResizecom uma janela de tela cheia, pois o layout nunca é redimensionado.
Ionoclast Brigham
triger algumas vezes.
Zionpi 26/05
22

Como a resposta de @ amalBit, registre um ouvinte no layout global e calcule a diferença entre o fundo visível do dectorView e o fundo proposto, se a diferença for maior que algum valor (altura do IME), acreditamos que o IME esteja em alta:

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

o limiar de altura 100 é a altura mínima calculada do IME.

Isso funciona tanto para o AdjustPan quanto para o AdjustResize.

alexhilton
fonte
2
Estou prestes a puxar meu cabelo !! Você salvou meu cabelo;)
Vijay Singh Chouhan 27/03
1
É a única boa resposta aqui, funciona perfeitamente no teclado macio, obrigado
Z3nk 23/01
12

A solução de Nebojsa quase funcionou para mim. Quando cliquei em um EditText de várias linhas, ele sabia que o teclado era exibido, mas quando comecei a digitar dentro do EditText, o realHeight e o proposHeight ainda eram os mesmos, então não sabia que o teclado ainda era exibido. Fiz uma pequena modificação para armazenar a altura máxima e funciona bem. Aqui está a subclasse 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
fonte
10

Não tenho certeza se alguém postar isso. Achei esta solução simples de usar! . A classe SoftKeyboard está em gist.github.com . Porém, enquanto o teclado pop-up / oculto o retorno de chamada do evento, precisamos de um manipulador para fazer as coisas corretamente na interface do usuário:

/*
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
fonte
aqui está o Git para obter o SoftkeyBoard " gist.github.com/felHR85/… "
douarbou
9

Eu resolvo isso substituindo onKeyPreIme (int keyCode, KeyEvent evento) no meu 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
fonte
Como usá-lo em Fragmento ou Atividade @qbait?
Maulik Dodia
Não funciona, só pode ser chamado quando deixo a página no meu caso.
DysaniazzZ 03/08/19
este é o método do EditText, consulte esta resposta: stackoverflow.com/a/5993196/2093236
Dmide
4

Eu tenho uma espécie de truque para fazer isso. Embora não pareça haver uma maneira de detectar quando o teclado virtual é exibido ou oculto, é possível detectar quando ele está prestes a ser exibido ou oculto, definindo um OnFocusChangeListenerno EditTextque você está ouvindo.

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: Uma coisa a ter em atenção com esse hack é que esse retorno de chamada é acionado imediatamente quando o EditTextganho ou a perda de foco. Isso realmente dispara antes que o teclado virtual seja exibido ou oculto. A melhor maneira que encontrei para fazer alguma coisa depois que o teclado é exibido ou oculto é usar Handlerae atrasar algo ~ 400ms, assim:

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);
        }
    });
poisondminds
fonte
1
Caso contrário, não funciona. OnFocusChangeListenerapenas informa se o EditTextfoco foi alterado após a alteração do estado. Mas o IMEpode estar oculto quando EditTexttem foco, como detectar este caso?
DysaniazzZ 03/08/19
2

Eu resolvi o problema na codificação traseira do textview de linha única.

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);
    }
}
user2462737
fonte
2
Não funciona no android: windowSoftInputMode = "AdjustPan". Eu queria que minha tela não ficasse reduzida depois que o teclado virtual aparecer. Pode dizer qualquer correção para que ele funciona mesmo para adjustPan
Shirish Herwade
Quando a função oculta / mostra, esse método de ouvinte está chamando duas ou três vezes. Eu não sei exatamente o que é o problema.
Jagveer Singh Rajput
2

Você também pode verificar o primeiro preenchimento de fundo filho do DecorView. Será definido como um valor diferente de zero quando o teclado for exibido.

@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
fonte
1

Ocultar | Mostrar eventos do teclado podem ser ouvidos através de um simples hack no 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
                }
            }
        });

Aqui activityRootView é a visualização raiz da sua atividade.

Varun Verma
fonte
minha heightDiff é de 160 no início e 742 com kbd, então eu tive que introduzir e definir initialHeightDiff no início
djdance
0

usando viewTreeObserver para obter facilmente o evento do 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 é sua opinião comoedit_text.parent

Geet Thakur
fonte
-2

A resposta de Nebojsa Tomcic não foi útil para mim. Eu tenho RelativeLayoutcom TextViewe AutoCompleteTextViewdentro dele. Preciso rolar TextViewpara baixo quando o teclado é exibido e quando está oculto. Para fazer isso, eu substituí o onLayoutmétodo e funciona bem para mim.

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
fonte