Tuesday, August 2, 2011

The Soft Keyboard

Android's APIs do not give us a method with which we can determine if the soft keyboard is showing or not.  We can programmatically request that the keyboard be shown or hidden with simple API calls, but no such calls exist for determining if the keyboard is visible or not.

With a simple trick, we can devise a way by which we can reliably determine if the keyboard is visible and even be notified when it is shown or hidden.

For this, we look to the android:windowSoftInputMode attribute of an Activity.  When set to the value "adjustResize", Android automatically changes the height of our activity's window to accommodate for the soft keyboard.  That is, when the keyboard is shown, our activity's window height is decreased by the height of the keyboard.  Likewise, when the keyboard is hidden, our activity's window height is increased by the height of the keyboard.

Knowing that Android will resize our window (and thus our top level view) when the keyboard is shown or hidden, we can create a top level layout which handles layout changes and determines if the soft keyboard is visible by comparing its height against the height of the display - if the height of the layout is less than the height of the display (minus the status bar height), we know that the keyboard is showing.

A sample top level layout might be defined as:

package com.cannedcoding;
public class KeyboardAwareLinearLayout extends LinearLayout {
  private boolean mKeyboardVisible = false;

  public boolean isKeyboardVisible() {
    return mKeyboardVisible;
  }

  @Override
  protected void onLayout(boolean changed, int l, int t, int r,
    int b) {
    super.onLayout(changed, l, t, r, b);

    updateKeyboardVisible();
  }

  private void updateKeyboardVisible() {
    int viewHeight = getHeight();
    int displayHeight = getDisplayHeight();

    //For the purposes of making this example more concise, 

    //we'll use a fixed max size for our status bar.
    //In practice, this should be determined programmatically
    int maxStatusBarHeight = 38;

    mKeyboardVisible =
        (viewHeight + maxStatusBarHeight < displayHeight);
  }

  private int getDisplayHeight() {
    WindowManager windowManager = (WindowManager) getContext()
      .getSystemService(Context.WINDOW_SERVICE);

    Display display = windowManager.getDefaultDisplay();

    return display.getHeight();
  }
}


Then, in our activity's layout xml, we define our top level layout as:

<?xml version="1.0" encoding="utf-8"?>
<com.cannedcoding.KeyboardAwareLinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/top_level_layout"
  android:layout_height="fill_parent"
  android:layout_width="fill_parent">
  <!-- Child Views -->
</com.cannedcoding.KeyboardAwareLinearLayout>


Note that we define our view's height to be "fill_parent" ("match_parent" in later versions of Android OS) as we want to ensure that our view attempts to fill the entire window.  This behavior allows us to compare the height of the view to the height of the display and determine the keyboard visibility.

Finally, in our activity, we simply find the top level view and voila, we can determine if our keyboard is visible.

KeyboardAwareLinearLayout layout =
  (KeyboardAwareLinearLayout)
  findViewById(R.id.top_level_layout);

layout.isKeyboardVisible();


If we would like to be notified when the keyboard is shown or hidden, we can simply define an abstract class:

public abstract class KeyboardVisibleChangedHandler {
  abstract public void onKeyboardVisibleChanged(boolean
    keyboardVisible);
}


We then update our KeyboardAwareLinearLayout class to contain a list of said handlers as well as methods for adding / removing them.

Finally, we update our updateKeyboardVisible method:

private void updateKeyboardVisible() {
  int viewHeight = getHeight();
  int displayHeight = getDisplayHeight();
  int maxStatusBarHeight = 38;

  boolean oldValue = mKeyboardVisible;

  mKeyboardVisible =
    (viewHeight + maxStatusBarHeight < displayHeight);

  if (oldValue != mKeyboardVisible) {
    //for each KeyboardVisibleChangedHandler, call
    //onKeyboardVisibleChanged(mKeyboardVisible)
  }
}

No comments:

Post a Comment