Как проверить видимость программной клавиатуры в Android? - PullRequest
485 голосов
/ 27 января 2010

Мне нужно сделать очень простую вещь - выяснить, отображается ли программная клавиатура. Возможно ли это в Android?

Ответы [ 38 ]

3 голосов
/ 01 февраля 2012

Я обнаружил, что комбинация метода @ Reuben_Scratton и метода @ Йогеша, кажется, работает лучше всего. Объединение их методов приведет к чему-то вроде этого:

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
    }
  }
});
3 голосов
/ 15 февраля 2018

Существует также решение с системными вставками, но оно работает только с API >= 21 (Android L). Скажем, у вас есть BottomNavigationView, который является дочерним по отношению к LinearLayout, и вам нужно скрыть его, когда отображается клавиатура:

> LinearLayout
  > ContentView
  > BottomNavigationView

Все, что вам нужно сделать, это расширить LinearLayout таким образом:

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

Идея состоит в том, что при отображении клавиатуры системные вставки меняются с довольно большим значением .bottom.

3 голосов
/ 22 мая 2018

Для этого может помочь скрытый метод, InputMethodManager.getInputMethodWindowVisibleHeight. Но я не знаю, почему он спрятан.

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()
  }
}
2 голосов
/ 22 февраля 2014

Я использовал небольшой вариант ответа Рубана, который оказался более полезным в определенных обстоятельствах, особенно с устройствами с высоким разрешением.

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
                }
            }
        });
2 голосов
/ 19 ноября 2014

Ни одно из этих решений не будет работать для Lollipop как есть.В Lollipop activityRootView.getRootView().getHeight() включена высота панели кнопок, а при измерении вида нет.Я адаптировал лучшее / простое решение выше для работы с 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
    }
  }
});
2 голосов
/ 16 декабря 2016

Это было всегда с точки зрения компьютера, но этот вопрос все еще невероятно актуален! Итак, я взял приведенные выше ответы и немного их объединил и уточнил ...

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

Это работает для меня.

1 голос
/ 21 августа 2017

Мне было трудно поддерживать состояние клавиатуры при изменении ориентации фрагментов в окне просмотра. Я не уверен, почему, но это только кажется странным и действует не так, как в стандартном действии.

Чтобы сохранить состояние клавиатуры в этом случае, сначала вы должны добавить android:windowSoftInputMode = "stateUnchanged" к вашему AndroidManifest.xml. Вы можете заметить, однако, что это на самом деле не решает всей проблемы - клавиатура не открывалась для меня, если она была открыта до изменения ориентации. Во всех остальных случаях поведение казалось правильным.

Затем нам нужно реализовать одно из упомянутых здесь решений. Самым чистым из найденных мной был Джордж Майсурадзе - используйте логический обратный вызов из hideSoftInputFromWindow:

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

Я сохранил это значение в методе onSaveInstanceState моего фрагмента и получил его onCreate. Затем я принудительно показал клавиатуру в onCreateView, если она имела значение true (она возвращает true, если клавиатура видна до фактического ее скрытия до уничтожения фрагмента).

1 голос
/ 17 октября 2014

Мой ответ в основном совпадает с ответом Качи, но я обернул его в хороший вспомогательный класс, чтобы очистить то, как он используется в моем приложении.

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

Вы можете использовать это для обнаружения изменений клавиатуры в любом месте приложения, как это:

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

Примечание: используйте только один из «зарегистрированных» вызовов. Все они работают одинаково и только для удобства

1 голос
/ 24 октября 2017

Попробуйте это:

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 ... \\
        }
    }
});
1 голос
/ 04 июня 2015

Я только что столкнулся с ошибкой при использовании большинства из приведенных выше решений, которые предлагают добавить фиксированное число.

S4 имеет высокое значение dpi, в результате чего высота навигационной панели составляет 100 пикселей, поэтому мое приложение думает, что клавиатура постоянно открыта.

Таким образом, после выпуска всех новых телефонов высокого разрешения, я считаю, что использование жестко закодированного значения не является хорошей идеей в долгосрочной перспективе.

Лучший подход, который я нашел после некоторого тестирования на различных экранах и устройствах, заключался в использовании процента. Получите разницу между decorView и содержимым вашего приложения, а затем проверьте процент этой разницы. Из статистики, которую я получил, большинство навигационной панели (независимо от размера, разрешения и т. Д.) Будет занимать от 3% до 5% экрана. Где, как будто клавиатура открыта, она занимала от 47% до 55% экрана.

В качестве вывода я решил проверить, составляет ли разница больше 10%, тогда я предполагаю, что клавиатура открыта.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...