Como verificar a visibilidade do teclado do software no Android?


516

Eu preciso fazer uma coisa muito simples - descobrir se o teclado do software é mostrado. Isso é possível no Android?


9
Embora a resposta de Reuben Scratton seja boa, parece quebrada em um tablet. Substituí a verificação diff> 128 por diff> screenHeight / 3.
— kingston

2
A resposta de Reuben Scratton foi boa, mas eu exigi o ajuste de KaChi para realmente usá-lo.
— Cullan

1
Por que o Google não faz com que um método interno padrão funcione para todos os aplicativos de teclado?
— Fruit

4
Ainda me irmãos, que este não é um sistema funciona ...
— longi

1
É absolutamente louco que essa API ainda esteja faltando 10 anos depois . Muito feliz por me afastar do Android.
— Reuben Scratton 27/01

Respostas:


674

Nova resposta adicionada em 25 de janeiro de 2012

Desde que escrevi a resposta abaixo, alguém me informou sobre a existência do ViewTreeObserver e dos amigos, APIs que estão ocultas no SDK desde a versão 1.

Em vez de exigir um tipo de layout personalizado, uma solução muito mais simples é fornecer um ID conhecido à visualização raiz da atividade, digamos @+id/activityRoot, conectar um GlobalLayoutListener ao ViewTreeObserver e, a partir daí, calcular o diferencial de tamanho entre a raiz da visualização da atividade e o tamanho da janela:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
        if (heightDiff > dpToPx(this, 200)) { // if more than 200 dp, it's probably a keyboard...
            // ... do something here
        }
     }
});

Usando um utilitário como:

public static float dpToPx(Context context, float valueInDp) {
    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
}

Fácil!

Nota: Seu aplicativo deve definir esse sinalizador no Android Manifest, android:windowSoftInputMode="adjustResize"caso contrário, a solução acima não funcionará.

RESPOSTA ORIGINAL

Sim, é possível, mas é muito mais difícil do que deveria ser.

Se eu precisar me preocupar quando o teclado aparecer e desaparecer (o que é frequente), o que faço é personalizar minha classe de layout de nível superior em uma que substitua onMeasure(). A lógica básica é que, se o layout estiver preenchendo significativamente menos que a área total da janela, provavelmente um teclado virtual estará aparecendo.

import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.LinearLayout;

/*
 * LinearLayoutThatDetectsSoftKeyboard - a variant of LinearLayout that can detect when 
 * the soft keyboard is shown and hidden (something Android can't tell you, weirdly). 
 */

public class LinearLayoutThatDetectsSoftKeyboard extends LinearLayout {

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

    public interface Listener {
        public void onSoftKeyboardShown(boolean isShowing);
    }
    private Listener listener;
    public void setListener(Listener listener) {
        this.listener = listener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = MeasureSpec.getSize(heightMeasureSpec);
        Activity activity = (Activity)getContext();
        Rect rect = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        int statusBarHeight = rect.top;
        int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
        int diff = (screenHeight - statusBarHeight) - height;
        if (listener != null) {
            listener.onSoftKeyboardShown(diff>128); // assume all soft keyboards are at least 128 pixels high
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);       
    }

    }

Em seguida, na sua classe Activity ...

public class MyActivity extends Activity implements LinearLayoutThatDetectsSoftKeyboard.Listener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        LinearLayoutThatDetectsSoftKeyboard mainLayout = (LinearLayoutThatDetectsSoftKeyboard)findViewById(R.id.main);
        mainLayout.setListener(this);
        ...
    }


    @Override
    public void onSoftKeyboardShown(boolean isShowing) {
        // do whatever you need to do here
    }

    ...
}

62
Isso não estava trabalhando para mim até que eu percebi que você deve definir o seguinte atributo na sua atividade: android: windowSoftInputMode = "adjustResize"
— ajh158

9
Parece estar fazendo o truque. Além disso, se você não souber o ID da visualização raiz, veja como você pode obtê-la:((ViewGroup) findViewById(android.R.id.content)).getChildAt(0)
— Goldsmith

8
Se você tentar isso usando a visualização raiz real ( android.R.id.content), poderá dizer com mais confiança que, em Systemvez de seu aplicativo, a entidade está alterando sua altura. Seria muito mais seguro para a equipe do Android nos dar uma pausa e nos informar pelo menos coisas básicas sobre a entrada do SoftKeyboard.
— Graeme

14
Cuidado que heightDiffsempre incluirá a altura da barra de ação. Na nova resposta que foi ignorada ao testar se essa altura é maior que alguma constante, mas 100 pixels não são suficientes para dispositivos xxhdpi como o Nexus 4. Considere converter esse valor em DPs se você realmente quiser usar esse trabalho hacky. por aí.
— Paul Lammertsma

8
Aviso: não funciona com WindowManager.LayoutParams.FLAG_FULLSCREEN e com um tema de tela cheia.
— VAV

303

Então, espero que isso ajude alguém.

A nova resposta que Reuben Scratton deu é ótima e muito eficiente, mas realmente só funciona se você definir o windowSoftInputMode para ajustarResize. Se você configurá-lo para ajustar o painel, ainda não é possível detectar se o teclado está visível ou não usando seu trecho de código. Para contornar isso, fiz essa pequena modificação no código acima.

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
    Rect r = new Rect();
    //r will be populated with the coordinates of your view that area still visible.
    activityRootView.getWindowVisibleDisplayFrame(r);

    int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
    if (heightDiff > 0.25*activityRootView.getRootView().getHeight()) { // if more than 25% of the screen, its probably a keyboard...
        ... do something here
    }
 }
}); 

1
Este é o que funcionou para mim. Eu estava tentando detectar o estado do teclado de um costume TwoDScrollerViewsemelhante ao stackoverflow.com/a/5224088/530513, embora com o zoom também. O filho não era um ImageViewlayout simples, mas personalizado (estende RelativeLayout), mas não conseguiu detectar o teclado usando a solução recomendada, apesar da configuração android:windowSoftInputMode="adjustResize". Obrigado!
— 22412 David O'Meara

1
Obrigado, obrigado, obrigado! AdjustResize simplesmente não é viável para o meu aplicativo e sua solução funcionou perfeitamente.
— Dwemthy

1
Trabalha com ActionBare ActionBarSherlock. Muito obrigado! By the way, existe um método r.height():) #
— 555 Dmitry Zaytsev

1
Anexarei uma recompensa aqui dentro de 23 horas para marcar esta resposta de alguma forma.
— Dmitry Zaytsev

9
heightDiff > root.getRootView().getHeight() / 4É um bom valor trabalhar com dispositivos de alta resolução. 100px é muito curto. no Nexus 5 com resolução 1080x1920, 1920 - (996-75)>? 100 = 999 1920 - (1776-75)>? 100 = 219 // o teclado está ativo na galaxy s2 com 480x800 res, 800 - (800-38)>? 100 = 38800 - (410-38)>? 100 = 428 // o teclado está ativado, o número mágico 100px não é bom o suficiente.
— precisa saber é o seguinte

55

Sempre foi em termos de computador, mas essa questão ainda é incrivelmente relevante!

Então, eu peguei as respostas acima e as combinei e refinei um pouco ...

public interface OnKeyboardVisibilityListener {


    void onVisibilityChanged(boolean visible);
}

public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
    final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);

    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        private boolean wasOpened;

        private final int DefaultKeyboardDP = 100;

        // From @nathanielwolf answer...  Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
        private final int EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);

        private final Rect r = new Rect();

        @Override
        public void onGlobalLayout() {
            // Convert the dp to pixels.
            int estimatedKeyboardHeight = (int) TypedValue
                    .applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, activityRootView.getResources().getDisplayMetrics());

            // Conclude whether the keyboard is shown or not.
            activityRootView.getWindowVisibleDisplayFrame(r);
            int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
            boolean isShown = heightDiff >= estimatedKeyboardHeight;

            if (isShown == wasOpened) {
                Log.d("Keyboard state", "Ignoring global layout change...");
                return;
            }

            wasOpened = isShown;
            listener.onVisibilityChanged(isShown);
        }
    });
}

Funciona para mim :)

NOTA: Se você perceber que o DefaultKeyboardDP não se encaixa no seu dispositivo, reproduza o valor e publique um comentário para que todos saibam qual deve ser o valor ... eventualmente, obteremos o valor correto para todos os dispositivos!

Para mais detalhes, confira a implementação no Cyborg


2
+1 Muito obrigado !! Eu estava tentando as outras respostas, mas elas não funcionam. Então eu encontrei o seu e funciona como um encanto. Código incrível! : D
— Kevin van Mierlo

isso funciona apenas se você adicionar: android: windowSoftInputMode = "stateHidden | AdjustPan" ou android: windowSoftInputMode = "stateHidden | AdjustResize" Obrigado !!!!
— Lena Bru

Você tem certeza? Se servidores de memória, recebi os eventos corretamente também quando o android: windowSoftInputMode tinha seu valor padrão ... a única coisa que não deu certo é o comportamento da tela, então eu a encolhi manualmente ...
— TacB0sS

2
uma pequena correção: private final int EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT> = Build.VERSION_CODES.LOLLIPOP? 48: 0);
— binaryKarmic

2
Excelente!! Não importa que "windowSoftInputMode" esteja definido como "AdjustPan" / "AdjustResize" / "AdjustPan | stateHidden" / "AdjustResize | stateHidden", ou mesmo sem essa opção, ele sempre funciona! Testado em XiaoMi 8.
— Zhou Hongbo

52

Desculpe pela resposta tardia, mas eu criei uma turma auxiliar para lidar com eventos de abertura / fechamento com ouvintes notificadores e outras coisas úteis, pode ser que alguém ache útil:

import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;

import java.util.LinkedList;
import java.util.List;

public class SoftKeyboardStateWatcher implements ViewTreeObserver.OnGlobalLayoutListener {

    public interface SoftKeyboardStateListener {
        void onSoftKeyboardOpened(int keyboardHeightInPx);
        void onSoftKeyboardClosed();
    }

    private final List<SoftKeyboardStateListener> listeners = new LinkedList<SoftKeyboardStateListener>();
    private final View activityRootView;
    private int        lastSoftKeyboardHeightInPx;
    private boolean    isSoftKeyboardOpened;

    public SoftKeyboardStateWatcher(View activityRootView) {
        this(activityRootView, false);
    }

    public SoftKeyboardStateWatcher(View activityRootView, boolean isSoftKeyboardOpened) {
        this.activityRootView     = activityRootView;
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    public void onGlobalLayout() {
        final Rect r = new Rect();
        //r will be populated with the coordinates of your view that area still visible.
        activityRootView.getWindowVisibleDisplayFrame(r);

        final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
        if (!isSoftKeyboardOpened && heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
            isSoftKeyboardOpened = true;
            notifyOnSoftKeyboardOpened(heightDiff);
        } else if (isSoftKeyboardOpened && heightDiff < 100) {
            isSoftKeyboardOpened = false;
            notifyOnSoftKeyboardClosed();
        }
    }

    public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) {
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
    }

    public boolean isSoftKeyboardOpened() {
        return isSoftKeyboardOpened;
    }

    /**
     * Default value is zero {@code 0}.
     *
     * @return last saved keyboard height in px
     */
    public int getLastSoftKeyboardHeightInPx() {
        return lastSoftKeyboardHeightInPx;
    }

    public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.add(listener);
    }

    public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.remove(listener);
    }

    private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) {
        this.lastSoftKeyboardHeightInPx = keyboardHeightInPx;

        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardOpened(keyboardHeightInPx);
            }
        }
    }

    private void notifyOnSoftKeyboardClosed() {
        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardClosed();
            }
        }
    }
}

Exemplo de uso:

final SoftKeyboardStateWatcher softKeyboardStateWatcher 
    = new SoftKeyboardStateWatcher(findViewById(R.id.activity_main_layout);

// Add listener
softKeyboardStateWatcher.addSoftKeyboardStateListener(...);
// then just handle callbacks

2
A classe não é pequena, mas a implementação certamente é :). Graças Vou tentar isso :)
— Atul O Holic

1
Ping-me se você tem problemas com ele :) Eu usado com sucesso em 2 projectos
— Artem Zinnatullin

Depois de tentar muitos exemplos acima e encontrar problemas menores, este foi o que melhor funcionou para mim em muitos dispositivos diferentes (incluindo xxhdpi). Além disso, está em sua própria classe reutilizável. Eu o convertei para ser usado no mono droid.
— kheit

Alguns teclados, algumas vezes, têm uma linha extra de teclas personalizadas na parte superior (por exemplo, palavras previstas). Aparentemente, eles não fazem parte do próprio teclado, pois o uso do getLastKeyboardHeightInPx()não inclui a altura dessa linha. Você conhece uma maneira de levar isso em consideração também?
— ygesher

Isso só funciona se você estiver pronto para comprometer a alteração da altura do layout quando o teclado aparecer. direita?
— M. Usman Khan

33

Algumas melhorias para evitar detectar erroneamente a visibilidade do teclado virtual em dispositivos de alta densidade:

  1. O limite da diferença de altura deve ser definido como 128 dp , não 128 pixels .
    Consulte o documento de design do Google sobre Metrics and Grid , 48 dp é o tamanho confortável para o objeto de toque e 32 dp é o mínimo para os botões. O teclado virtual genérico deve incluir 4 linhas de botões, para que a altura mínima do teclado seja: 32 dp * 4 = 128 dp , o que significa que o tamanho do limite deve ser transferido para pixels multiplicando a densidade do dispositivo. Para dispositivos xxxhdpi (densidade 4), o limite de altura do teclado virtual deve ser 128 * 4 = 512 pixels.

  2. Diferença de altura entre a vista raiz e sua área visível:
    altura da vista raiz - altura da barra de status - altura do quadro visível = vista inferior da raiz - fundo visível do quadro, uma vez que a altura da barra de status é igual à parte superior do quadro visível da vista raiz.

    private final String TAG = "TextEditor";
    private TextView mTextEditor;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_editor);
        mTextEditor = (TextView) findViewById(R.id.text_editor);
        mTextEditor.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                isKeyboardShown(mTextEditor.getRootView());
            }
        });
    }
    
    private boolean isKeyboardShown(View rootView) {
        /* 128dp = 32dp * 4, minimum button height 32dp and generic 4 rows soft keyboard */
        final int SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD = 128;
    
        Rect r = new Rect();
        rootView.getWindowVisibleDisplayFrame(r);
        DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
        /* heightDiff = rootView height - status bar height (r.top) - visible frame height (r.bottom - r.top) */
        int heightDiff = rootView.getBottom() - r.bottom;
        /* Threshold size: dp to pixels, multiply with display density */
        boolean isKeyboardShown = heightDiff > SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD * dm.density;
    
        Log.d(TAG, "isKeyboardShown ? " + isKeyboardShown + ", heightDiff:" + heightDiff + ", density:" + dm.density
                + "root view height:" + rootView.getHeight() + ", rect:" + r);
    
        return isKeyboardShown;
    }

4
Isso merece ser a resposta aceita. Ignorar a densidade me deu resultados muito diferentes em dispositivos com diferentes formatos, mas tamanhos de pixel semelhantes. Obrigado!
— Ginger McMurray

2
Obrigado ... esta é uma condição melhor!
— TbB0sS

1
Excelente. O método isKeyboardShown () é o que precisamos. Obrigado
— Danylo Volokh

Também está funcionando em 2020. Resposta perfeita. Eu tentei todo o código, mas isso funciona perfeitamente.
— Mitesh Jain

8

Usei um pouco de tempo para descobrir isso ... Eu executei algumas CastExceptions, mas descobri que você pode substituir o LinearLayout no layout.xml pelo nome da classe.

Como isso:

<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent"
    xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/llMaster">

<com.ourshoppingnote.RelativeLayoutThatDetectsSoftKeyboard android:background="@drawable/metal_background"
    android:layout_width="fill_parent" android:layout_height="fill_parent"
    android:id="@+id/rlMaster" >
    <LinearLayout android:layout_width="fill_parent"
        android:layout_height="1dip" android:background="@drawable/line"></LinearLayout>

          ....

</com.ourshoppingnote.RelativeLayoutThatDetectsSoftKeyboard>    


</LinearLayout>

Dessa forma, você não enfrenta nenhum problema de elenco.

... e se você não quiser fazer isso em todas as páginas, recomendo que você use "MasterPage no Android". Veja o link aqui: http://jnastase.alner.net/archive/2011/01/08/ldquomaster-pagesrdquo-in-android.aspx


Tenha cuidado ao colar isso em seu XML se você não tiver o mesmo nome de pacote / classe. O Eclipse decide congelar e você precisa desligá-lo. Um produto tão profissional. / s
— Spencer Ruport

5
@SpencerRuport, é por isso que é grátis.
— Cody

@DoctorOreo - obtenha o IntelliJ. É grátis e não é ruim.
— 26413 Mark

@ Mark - alguns meses depois que postei isso, eu realmente experimentei o IntelliJ. É muito melhor, IMO, do que o Eclipse. Todos os seus produtos (na maior parte) acho que são excelentes. Eu até comprei alguns.
— Cody

Desculpe por reviver um tópico de comentário tão antigo. Fico feliz que você esteja usando e gostando. Adoro usar o IntelliJ, bem como o AppCode para iOS e o PyCharm para Python. Felicidades!
— 29413 Mark


5

A idéia é que, se você precisar ocultar o teclado e verificar o estado de entrada suave ao mesmo tempo, use a seguinte solução:

public boolean hideSoftInput() {
    InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
    return imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);
}

Este método retorna true se o teclado foi mostrado antes de ocultar.


Esta é a única obras sem o uso de altura e todos ... Obrigado ... u salvou a minha vez ...
— VJI

4

Descobri que uma combinação do método de @ Reuben_Scratton junto com o método de @ Yogesh parece funcionar melhor. A combinação de seus métodos produziria algo como isto:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
  @Override
  public void onGlobalLayout() {
    if (getResources().getConfiguration().keyboardHidden == Configuration.KEYBOARDHIDDEN_NO) { // Check if keyboard is not hidden
       // ... do something here
    }
  }
});

sempre Configuration.KEYBOARDHIDDEN_NO.
— fantouch

4

Você pode observar a ocultação do teclado virtual usando decorView da atividade.

public final class SoftKeyboardUtil {
    public static final String TAG = "SoftKeyboardUtil";
    public static void observeSoftKeyBoard(Activity activity , final OnSoftKeyBoardHideListener listener){
        final View decorView = activity.getWindow().getDecorView();
        decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect rect = new Rect();
                decorView.getWindowVisibleDisplayFrame(rect);
                int displayHight = rect.bottom - rect.top;
                int hight = decorView.getHeight();
                boolean hide = (double)displayHight / hight > 0.8 ;
                if(Log.isLoggable(TAG, Log.DEBUG)){
                    Log.d(TAG ,"DecorView display hight = "+displayHight);
                    Log.d(TAG ,"DecorView hight = "+ hight);
                    Log.d(TAG, "softkeyboard visible = " + !hide);
                }

                listener.onSoftKeyBoardVisible(!hide);

            }
        });
    }



    public interface OnSoftKeyBoardHideListener{
        void onSoftKeyBoardVisible(boolean visible);
    }
}

4

Em vez de assumir a diferença de codificação, eu fiz algo assim, pois eu tinha opções de menu no meu aplicativo.

final View root= findViewById(R.id.myrootview); 
root.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
    public void onGlobalLayout() {
        int heightDiff = root.getRootView().getHeight() - root.getHeight();

        Rect rectgle= new Rect();
        Window window= getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
        int contentViewTop=                     
          window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
        if(heightDiff <= contentViewTop){
            //Soft KeyBoard Hidden
        }else{
            //Soft KeyBoard Shown
        }
     }
});

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

4

Também existe solução com inserções do sistema, mas funciona apenas com API >= 21( Android L). Digamos que você tenha BottomNavigationView, que é filho LinearLayoute precise ocultá-lo quando o teclado for exibido:

> LinearLayout
  > ContentView
  > BottomNavigationView

Tudo que você precisa fazer é estender LinearLayoutda seguinte maneira:

public class KeyboardAwareLinearLayout extends LinearLayout {
    public KeyboardAwareLinearLayout(Context context) {
        super(context);
    }

    public KeyboardAwareLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyboardAwareLinearLayout(Context context,
                                     @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

    @Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        int childCount = getChildCount();
        for (int index = 0; index < childCount; index++) {
            View view = getChildAt(index);
            if (view instanceof BottomNavigationView) {
                int bottom = insets.getSystemWindowInsetBottom();
                if (bottom >= ViewUtils.dpToPx(200)) {
                    // keyboard is shown
                    view.setVisibility(GONE);
                } else {
                    // keyboard is hidden
                    view.setVisibility(VISIBLE);
                }
            }
        }
        return insets;
    }
}

A idéia é que, quando o teclado é mostrado, as inserções do sistema são alteradas com um .bottomvalor bastante grande .


4

Existe um método oculto que pode ajudar nisso InputMethodManager.getInputMethodWindowVisibleHeight,. Mas não sei por que está escondido.

import android.content.Context
import android.os.Handler
import android.view.inputmethod.InputMethodManager

class SoftKeyboardStateWatcher(private val ctx: Context) {
  companion object {
    private const val DELAY = 10L
  }

  private val handler = Handler()
  private var isSoftKeyboardOpened: Boolean = false

  private val height: Int
    get() {
      val imm = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
      val method = imm.javaClass.getMethod("getInputMethodWindowVisibleHeight")
      method.isAccessible = true
      return method.invoke(imm) as Int
    }

  private val task: Runnable by lazy {
    Runnable {
      start()
      if (!isSoftKeyboardOpened && height > 0) {
        isSoftKeyboardOpened = true
        notifyOnSoftKeyboardOpened(height)
      } else if (isSoftKeyboardOpened && height == 0) {
        isSoftKeyboardOpened = false
        notifyOnSoftKeyboardClosed()
      }
    }
  }

  var listener: SoftKeyboardStateListener? = null

  interface SoftKeyboardStateListener {
    fun onSoftKeyboardOpened(keyboardHeightInPx: Int)
    fun onSoftKeyboardClosed()
  }

  fun start() {
    handler.postDelayed(task, DELAY)
  }

  fun stop() {
    handler.postDelayed({
      if (!isSoftKeyboardOpened) handler.removeCallbacks(task)
    }, DELAY * 10)
  }

  private fun notifyOnSoftKeyboardOpened(keyboardHeightInPx: Int) {
    listener?.onSoftKeyboardOpened(keyboardHeightInPx)
  }

  private fun notifyOnSoftKeyboardClosed() {
    listener?.onSoftKeyboardClosed()
  }
}

Se alguém precisar disso - ele funciona no Xamarin, o nome do método é exatamente o mesmo e precisa ser acessado da mesma maneira - através da propriedade Class no InputMethodManager.
— Konstantin Severy

Tenha cuidado ao usar isso, pois é uma API não suportada (está oculta por um motivo) e, para iniciantes, não funciona no KitKat.
— Daniele Ricci

3

Nenhuma dessas soluções funcionará para o Lollipop como está. No Lollipop activityRootView.getRootView().getHeight()inclui a altura da barra de botões, enquanto a medição da vista não. Eu adaptei a melhor / mais simples solução acima para trabalhar com o Lollipop.

    final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
  @Override
  public void onGlobalLayout() {
    Rect r = new Rect();
    //r will be populated with the coordinates of your view that area still visible.
    activityRootView.getWindowVisibleDisplayFrame(r);

    int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
    Resources res = getResources();
    // The status bar is 25dp, use 50dp for assurance
    float maxDiff =
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, res.getDisplayMetrics());

    //Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      float buttonBarHeight =
          TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, res.getDisplayMetrics());
      maxDiff += buttonBarHeight;
    }
    if (heightDiff > maxDiff) { // if more than 100 pixels, its probably a keyboard...
      ...do something here
    }
  }
});

Por que uma solução semelhante de stackoverflow.com/a/18992807/2914140 funcionou para você e como sua solução difere?
— CoolMind

3

Acabei de encontrar um bug ao usar a maioria das soluções acima que sugerem a adição de um número fixo.

S4 é tem um dpi alto que resultou na altura da barra de navegação sendo 100px, portanto, meu aplicativo pensa que o teclado está aberto o tempo todo.

Portanto, com todos os novos telefones de alta resolução sendo lançados, acredito que usar um valor codificado não é uma boa idéia a longo prazo.

Uma abordagem melhor que encontrei após alguns testes em várias telas e dispositivos foi usar porcentagem. Obtenha a diferença entre o decorView e o conteúdo do seu aplicativo e depois verifique qual é a porcentagem dessa diferença. A partir das estatísticas que obtive, a maioria das barras de navegação (independentemente do tamanho, resolução etc.) levará entre 3% a 5% da tela. Onde, como se o teclado estivesse aberto, estava ocupando entre 47% a 55% da tela.

Como conclusão, minha solução foi verificar se o diff é superior a 10%, e presumo que seja um teclado aberto.


3

Usei uma pequena variante da resposta de Reuban, que provou ser mais útil em determinadas circunstâncias, especialmente com dispositivos de alta resolução.

final View activityRootView = findViewById(android.R.id.content);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
        new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int heightView = activityRootView.getHeight();
                int widthView = activityRootView.getWidth();
                if (1.0 * widthView / heightView > 3) {
                    //Make changes for Keyboard not visible
                } else {
                    //Make changes for keyboard visible
                }
            }
        });

O que é isso R.id.activityRoot
— ranjith

2
em vez de criar e usar R.id.activityRoot, você pode simplesmente usar android.R.id.contentexatamente o que você precisa.
— Marcin Orlowski

3

Sempre foi em termos de computador, mas essa questão ainda é incrivelmente relevante! Então, eu peguei as respostas acima e as combinei e refinei um pouco ...

public interface OnKeyboardVisibilityListener {
    void onVisibilityChanged(boolean visible);
}

public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
    final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        private boolean wasOpened;

    private final Rect r = new Rect();

        @Override
        public void onGlobalLayout() {
            activityRootView.getWindowVisibleDisplayFrame(r);

            int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
            boolean isOpen = heightDiff > 100;
            if (isOpen == wasOpened) {
                logDebug("Ignoring global layout change...");
                return;
            }

            wasOpened = isOpen;
            listener.onVisibilityChanged(isOpen);
        }
    });
}

Funciona para mim.


3

Tente o seguinte:

final View activityRootView = getWindow().getDecorView().getRootView();
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        Rect r = new Rect();
        //r will be populated with the coordinates of your view that area still visible.
        activityRootView.getWindowVisibleDisplayFrame(r);

        int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
        if (heightDiff < activityRootView.getRootView().getHeight() / 4 ) { // if more than 100 pixels, its probably a keyboard...
             // ... do something here ... \\
        }
    }
});

2

Minha resposta é basicamente a mesma que a resposta de Kachi, mas envolvi-a em uma classe auxiliar agradável para limpar a maneira como é usada em todo o meu aplicativo.

import android.app.Activity;
import android.app.Fragment;
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;

/**
 * Detects Keyboard Status changes and fires events only once for each change
 */
public class KeyboardStatusDetector {
    KeyboardVisibilityListener visibilityListener;

    boolean keyboardVisible = false;

    public void registerFragment(Fragment f) {
        registerView(f.getView());
    }

    public void registerActivity(Activity a) {
        registerView(a.getWindow().getDecorView().findViewById(android.R.id.content));
    }

    public KeyboardStatusDetector registerView(final View v) {
        v.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Rect r = new Rect();
                v.getWindowVisibleDisplayFrame(r);

                int heightDiff = v.getRootView().getHeight() - (r.bottom - r.top);
                if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
                    /** Check this variable to debounce layout events */
                    if(!keyboardVisible) {
                        keyboardVisible = true;
                        if(visibilityListener != null) visibilityListener.onVisibilityChanged(true);
                    }
                } else {
                    if(keyboardVisible) {
                        keyboardVisible = false;
                        if(visibilityListener != null) visibilityListener.onVisibilityChanged(false);
                    }
                }
            }
        });

        return this;
    }

    public KeyboardStatusDetector setVisibilityListener(KeyboardVisibilityListener listener) {
        visibilityListener = listener;
        return this;
    }

    public static interface KeyboardVisibilityListener {
        public void onVisibilityChanged(boolean keyboardVisible);
    }
}

Você pode usar isso para detectar alterações no teclado em qualquer lugar do aplicativo, como este:

    new KeyboardStatusDetector()
            .registerFragment(fragment)  //register to a fragment 
            .registerActivity(activity)  //or register to an activity
            .registerView(view)          //or register to a view
            .setVisibilityListener(new KeyboardVisibilityListener() {
                @Override
                public void onVisibilityChanged(boolean keyboardVisible) {
                    if(keyboardVisible) {
                       //Do stuff for keyboard visible
                    }else {
                       //Do stuff for keyboard hidden
                    }
                }
            });

Nota: use apenas uma das chamadas "registrar". Todos eles funcionam da mesma forma e estão lá apenas por conveniência


2

você pode tentar isso, funciona muito bem para mim:

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

if (imm.isAcceptingText()) {
    //Software Keyboard was shown..
} else {
    //Software Keyboard was not shown..
}

1
Não use isso, ele retorna true se o teclado estava escondida usando de volta, mas o ponto de vista tem o foco, pelo menos no Marshmallow
— Maragues

2
sempre responde que é visível
— jose920405

2

Eu estava tendo dificuldade em manter o estado do teclado ao alterar a orientação dos fragmentos em um viewpager. Não sei por que, mas parece ser instável e age de maneira diferente de uma atividade padrão.

Para manter o estado do teclado nesse caso, primeiro você deve adicionar android:windowSoftInputMode = "stateUnchanged"ao seuAndroidManifest.xml . Você pode notar, no entanto, que isso realmente não resolve todo o problema - o teclado não foi aberto para mim se estivesse aberto anteriormente antes da alteração da orientação. Em todos os outros casos, o comportamento parecia estar correto.

Então, precisamos implementar uma das soluções mencionadas aqui. O mais limpo que encontrei foi o de George Maisuradze - use o retorno de chamada booleano de hideSoftInputFromWindow:

InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
return imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);

Guardei esse valor no onSaveInstanceStatemétodo do meu Fragment e o recuperei onCreate. Então, mostrei o teclado à força onCreateViewse tivesse um valor de true(ele retorna true se o teclado estiver visível antes de realmente ocultá-lo antes da destruição do fragmento).


1

Aqui está a minha solução e funciona. Em vez de procurar o tamanho do pixel, verifique se a altura da exibição do conteúdo mudou ou não:

// Scroll to the latest comment whenever the keyboard is shown
commentsContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        private int oldHeight;

        @Override
        public void onGlobalLayout() {
            int newHeight = commentsContent.getMeasuredHeight();
            if (newHeight < oldHeight) {
                // Check for the keyboard showing in case the height difference
                // is a result of orientation change
                if (isSoftKeyboardShowing(CommentsActivity.this)) {
                    // Keyboard is showing so scroll to the latest comment
                    scrollToLatestComment();
                }
            }
            oldHeight = newHeight;
        }

    });


public static boolean isSoftKeyboardShowing(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    return inputMethodManager.isActive();
}

inputMethodManager.isActive () é sempre retornando verdadeiro para mim, independentemente do teclado é se ou não
— EionRobb

1

Não crie nenhum código rígido. A melhor maneira é redimensionar suas visualizações enquanto estiver no Get Focus on EditText with KeyBord Show. Você pode fazer isso adicionando propriedades de redimensionamento à atividade no arquivo Manifest usando o código abaixo.

android:windowSoftInputMode="adjustResize"


1

Existe um método direto para descobrir isso. E, não requer nenhuma alteração de layout.
Portanto, ele também funciona no modo imersivo em tela cheia.

O truque é tentar ocultar ou mostrar o teclado virtual e capturar o resultado dessa tentativa.
Sem pânico, isso realmente não mostra ou oculta o teclado. Nós apenas pedimos o estado.

Para manter-se atualizado, você pode simplesmente repetir a operação, por exemplo, a cada 200 milissegundos, usando um manipulador.

Você encontra uma implementação aqui: https://stackoverflow.com/a/27567074/2525452


1

Eu acho que esse método irá ajudá-lo a descobrir se o teclado é visível ou não.

 public Boolean isSoftKeyBoardVisible(){
    InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);

    if (imm.isAcceptingText()) {
        Log.d(TAG,"Software Keyboard was shown");
        return true;
    } else {
        Log.d(TAG,"Software Keyboard was not shown");
        return false;
    }

}

sempre responde que é visível
— jose920405

0

A nova resposta de Reuben Scratton (calcule o HeightDiff int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight(); ) não funcionará em atividade se você definir o modo translúcido da barra de status.

se você usar barra de status translúcida, activityRootView.getHeight() nunca mudará o clima, o teclado virtual estará visível. sempre retornará a altura da atividade e a barra de status.

Por exemplo, o Nexus 4, Android 5.0.1, definido android:windowTranslucentStatuscomo true, retornará 1184 para sempre, até mesmo os ope. Se você definirandroid:windowTranslucentStatus como false, ele retornará Height corretamente, se for invisível, retornará 1134 (não inclui a barra de status) .Feche o ime, retornará 5xx talvez (depende da altura do ime)

Eu não sei o tempo, isso é um erro, tentei 4.4.4 e 5.0.1, o resultado é o mesmo.

Portanto, até agora, a segunda resposta mais acertada, a solução da Kachi será a maneira mais segura de calcular a altura da imagem. Aqui está uma cópia:

final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new        OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
activityRootView.getWindowVisibleDisplayFrame(r);

int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
    ... do something here
    }
 }
}); 

0

Um método que não precisa de um LayoutListener

No meu caso, gostaria de salvar o estado do teclado antes de substituir o meu fragmento. Eu chamo o método hideSoftInputFromWindow deonSaveInstanceState , que fecha o teclado e me retorna se o teclado estava visível ou não.

Este método é simples, mas pode alterar o estado do seu teclado.


0

Sei que este é um post antigo, mas acho que essa é a abordagem mais simples que conheço e meu dispositivo de teste é o Nexus 5. Não tentei em outros dispositivos. Espero que outras pessoas compartilhem sua abordagem se acharem que meu código não é bom :)

public static boolean isKeyboardShown(Context context, View view) {
        if (context == null || view == null) {
            return false;
        }
        InputMethodManager imm = (InputMethodManager) context
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        return imm.hideSoftInputFromWindow(view.getWindowToken(), 0); 
}

imm.hideSoftInputFromWindow retorna booleano.

Obrigado,


0
if (keyopen())
{
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY,0);            
}

A função acima é o que eu uso para verificar se um teclado está visível. Se for, então eu fecho.

Abaixo mostra os dois métodos necessários.

Primeiro, defina a altura da janela viável no onCreate.

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

//  add to onCreate method
    Rect rectgle= new Rect();
    Window window= getWindow();
    window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
    sheight= rectgle.bottom;
//

} 

Em seguida, adicione um método booleano que obtenha a altura da janela nessa instância. Se ele não corresponder ao original (supondo que você não o mude ao longo do caminho ...), o teclado estará aberto.

public boolean keyopen()
{
    Rect rectgle= new Rect();
    Window window= getWindow();
    window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
    int curheight= rectgle.bottom;

    if (curheight!=sheight)
    {
        return true;
    }
    else
    {
        return false;
    }
}

Frotz!


0

Eu sei o quão exato você pode determinar se o teclado está oculto ou não.

public int getStatusBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

public int getNavigationBarHeight() {
    int result = 0;
    int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

public boolean isKeyboardHidden() {
    int delta = mRootView.getRootView().getHeight() - mRootView.getHeight() - getNavigationBarHeight() - getStatusBarHeight()
            - getSupportActionBar().getHeight();
    return delta <= 0;
}

Isso funciona para tablets. Quando a barra de navegação é mostrada horizontalmente.

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.