I need to do a very simple thing - find out if the software keyboard is shown. Is this possible in Android?
There is a direct method to find this out. And, it does not require any Layout changes.
So, it works in immersive fullscreen mode, too.
The trick is that you try to hide or show the soft keyboard and capture the result of that try.
No panic, this does not really show or hide the keyboard. We just ask for the state.
To stay up-to-date, you can simply repeat the operation, e.g. every 200 milliseconds, using a Handler.
You find an implementation here: https://stackoverflow.com/a/27567074/2525452
So hopefully this helps someone out.
The new answer that Reuben Scratton gave is great and really efficient, but it really only works if you set your windowSoftInputMode to adjustResize. If you set it to adjustPan, it's still not possible to detect whether or not the keyboard is visible using his code snippet. To work around this, I made this tiny modification to the code above.
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.height();
if (heightDiff > 0.25*activityRootView.getRootView().getHeight()) { // if more than 25% of the screen, its probably a keyboard...
... do something here
}
}
});
A method that doesn't need a LayoutListener
In my case, I would like to save the state of the keyboard before replacing my Fragment. I call the method hideSoftInputFromWindow from onSaveInstanceState
, which closes the keyboard and returns me whether the keyboard was visible or not.
This method is straightforward but may change the state of your keyboard.
Sorry for the late answer, but I had created a little helper class to handle open/close events with notifying listeners and other useful things, may be someone would find it helpful:
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();
}
}
}
}
Usage example:
final SoftKeyboardStateWatcher softKeyboardStateWatcher
= new SoftKeyboardStateWatcher(findViewById(R.id.activity_main_layout);
// Add listener
softKeyboardStateWatcher.addSoftKeyboardStateListener(...);
// then just handle callbacks
Reuben Scratton's new answer (calculate the HeightDiff int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
) will not work in activity if you set the translucent status bar mode.
if you use translucent status bar , activityRootView.getHeight()
will never change weather the soft keyboard is visible. it will always return the height of activity and status bar.
For example, Nexus 4, Android 5.0.1, set android:windowTranslucentStatus
to true, it will return 1184 forever, even the ime have opend. If you set android:windowTranslucentStatus
to false, it will return Height correctly, if ime invisible,it return 1134(not include the status bar)。close the ime, it will return 5xx maybe (depends on ime's height)
I don't know weather this is a bug, I've try on 4.4.4 and 5.0.1, the result is same.
So, up to now, the second most agreed answer, Kachi's solution will be the most safe way to calcute the height of ime. Here's a copy:
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
}
}
});
NEW ANSWER added Jan 25th 2012
Since writing the below answer, someone clued me in to the existence of ViewTreeObserver and friends, APIs which have been lurking in the SDK since version 1.
Rather than requiring a custom Layout type, a much simpler solution is to give your activity's root view a known ID, say @+id/activityRoot
, hook a GlobalLayoutListener into the ViewTreeObserver, and from there calculate the size diff between your activity's view root and the window size:
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
}
}
});
Using a utility such as:
public static float dpToPx(Context context, float valueInDp) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
}
Easy!
Note:
Your application must set this flag in Android Manifest android:windowSoftInputMode="adjustResize"
otherwise above solution will not work.
ORIGINAL ANSWER
Yes it's possible, but it's far harder than it ought to be.
If I need to care about when the keyboard appears and disappears (which is quite often) then what I do is customize my top-level layout class into one which overrides onMeasure()
. The basic logic is that if the layout finds itself filling significantly less than the total area of the window, then a soft keyboard is probably showing.
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);
}
}
Then in your Activity class...
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
}
...
}