Mobile Development 14 min read

Android Soft Keyboard Height Detection and Layout Attachment Techniques

This article reviews three approaches to obtain the Android soft‑keyboard height—using ViewTreeObserver, a hidden PopupWindow, and WindowInsets—compares their pros and cons, shows complete Kotlin/Java implementations, and demonstrates how to attach UI elements to the keyboard for a smoother user experience.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Android Soft Keyboard Height Detection and Layout Attachment Techniques

In this tutorial we revisit several ways to acquire the height of the Android soft keyboard and how to make a layout follow the keyboard's movement, providing practical code examples and optimization tips.

Method 1 – ViewTreeObserver : By adding a global layout listener to the activity's content view, we calculate the invisible height of the decor view and derive the keyboard height. The full utility class is shown below.

public final class Keyboard1Utils {
    public static int sDecorViewInvisibleHeightPre;
    private static ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener;
    private static int sDecorViewDelta = 0;
    private static int getDecorViewInvisibleHeight(final Activity activity) {
        final View decorView = activity.getWindow().getDecorView();
        if (decorView == null) return sDecorViewInvisibleHeightPre;
        final Rect outRect = new Rect();
        decorView.getWindowVisibleDisplayFrame(outRect);
        int delta = Math.abs(decorView.getBottom() - outRect.bottom);
        if (delta <= getNavBarHeight()) { sDecorViewDelta = delta; return 0; }
        return delta - sDecorViewDelta;
    }
    public static void registerKeyboardHeightListener(final Activity activity, final KeyboardHeightListener listener) {
        int flags = activity.getWindow().getAttributes().flags;
        if ((flags & WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS) != 0) {
            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
        }
        final FrameLayout contentView = activity.findViewById(android.R.id.content);
        sDecorViewInvisibleHeightPre = getDecorViewInvisibleHeight(activity);
        onGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override public void onGlobalLayout() {
                int height = getDecorViewInvisibleHeight(activity);
                if (sDecorViewInvisibleHeightPre != height) {
                    listener.onKeyboardHeightChanged(height);
                    sDecorViewInvisibleHeightPre = height;
                }
            }
        };
        contentView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
    }
    public static void unregisterKeyboardHeightListener(Activity activity) {
        onGlobalLayoutListener = null;
        View contentView = activity.getWindow().getDecorView().findViewById(android.R.id.content);
        if (contentView == null) return;
        contentView.getViewTreeObserver().removeGlobalOnLayoutListener(onGlobalLayoutListener);
    }
    private static int getNavBarHeight() {
        Resources res = Resources.getSystem();
        int resourceId = res.getIdentifier("navigation_bar_height", "dimen", "android");
        return resourceId != 0 ? res.getDimensionPixelSize(resourceId) : 0;
    }
    public interface KeyboardHeightListener { void onKeyboardHeightChanged(int height); }
}

Method 2 – Hidden PopupWindow : A zero‑width, full‑height PopupWindow is created and a global layout listener on the popup view detects changes when the IME appears, allowing us to compute the keyboard height.

public class KeyboardHeightUtils extends PopupWindow {
    private KeyboardHeightListener mListener;
    private View popupView;
    private View parentView;
    private Activity activity;
    public KeyboardHeightUtils(Activity activity) {
        super(activity);
        this.activity = activity;
        LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        this.popupView = inflator.inflate(R.layout.keyboard_popup_window, null, false);
        setContentView(popupView);
        setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
        setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
        parentView = activity.findViewById(android.R.id.content);
        setWidth(0);
        setHeight(WindowManager.LayoutParams.MATCH_PARENT);
        popupView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override public void onGlobalLayout() {
                if (popupView != null) { handleOnGlobalLayout(); }
            }
        });
    }
    public void start() {
        parentView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
            @Override public void onViewAttachedToWindow(View view) {
                if (!isShowing() && parentView.getWindowToken() != null) {
                    setBackgroundDrawable(new ColorDrawable(0));
                    showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
                }
            }
            @Override public void onViewDetachedFromWindow(View view) {}
        });
    }
    public void close() { mListener = null; dismiss(); }
    public void registerKeyboardHeightListener(KeyboardHeightListener listener) { mListener = listener; }
    private void handleOnGlobalLayout() {
        Point screenSize = new Point();
        activity.getWindowManager().getDefaultDisplay().getSize(screenSize);
        Rect rect = new Rect();
        popupView.getWindowVisibleDisplayFrame(rect);
        int keyboardHeight = screenSize.y - rect.bottom;
        notifyKeyboardHeightChanged(keyboardHeight);
    }
    private void notifyKeyboardHeightChanged(int height) { if (mListener != null) { mListener.onKeyboardHeightChanged(height); } }
    public interface KeyboardHeightListener { void onKeyboardHeightChanged(int height); }
}

Method 3 – WindowInsets (Android R+) : For devices running Android 11 (API 30) and above, we can use WindowInsetsAnimation callbacks to receive real‑time keyboard height updates.

ViewCompat.setWindowInsetsAnimationCallback(window.decorView, new WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
    @Override public WindowInsetsCompat onProgress(WindowInsetsCompat insets, List
animations) {
        int keyboardHeight = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
        YYLogUtils.w("keyboardHeight = " + keyboardHeight);
        return insets;
    }
});
ViewCompat.getWindowInsetsController(findViewById(android.R.id.content))?.show(WindowInsetsCompat.Type.ime());

To support all versions we combine the approaches: devices below Android 11 use the ViewTreeObserver method, while Android 11+ devices use the WindowInsets method. The combined utility is shown below.

public final class Keyboard4Utils {
    public static void registerKeyboardHeightListener(Activity activity, KeyboardHeightListener listener) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            invokeAbove31(activity, listener);
        } else {
            invokeBelow31(activity, listener);
        }
    }
    @RequiresApi(Build.VERSION_CODES.R)
    private static void invokeAbove31(Activity activity, KeyboardHeightListener listener) {
        activity.getWindow().getDecorView().setWindowInsetsAnimationCallback(new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
            @Override public WindowInsets onProgress(@NonNull WindowInsets windowInsets, @NonNull List
list) {
                int height = windowInsets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
                listener.onKeyboardHeightChanged(height);
                return windowInsets;
            }
        });
    }
    private static void invokeBelow31(Activity activity, KeyboardHeightListener listener) {
        // reuse the ViewTreeObserver implementation from Keyboard1Utils
        Keyboard1Utils.registerKeyboardHeightListener(activity, listener);
    }
    public interface KeyboardHeightListener { void onKeyboardHeightChanged(int height); }
}

Finally, we demonstrate how to move a voice‑icon ImageView together with the keyboard by updating its bottom margin inside the height callback:

Keyboard1Utils.registerKeyboardHeightListener(this) { height ->
    YYLogUtils.w("Current keyboard height: $height")
    mIvVoice.updateLayoutParams
{ bottomMargin = height }
}

The article concludes that attaching layouts to the soft keyboard is a common pattern in Android apps, the third method provides the smoothest animation on newer devices, and a hybrid solution ensures reasonable compatibility across a wide range of Android versions.

mobile developmentUIAndroidKotlinKeyboard Heightsoft keyboard
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.