Mobile Development 20 min read

Understanding Android SOFT_INPUT_ADJUST Modes: Principles, Implementation and Usage

This article explains the five Android soft‑input adjustment modes (RESIZE, PAN, UNSPECIFIED, NOTHING and the method to obtain the visible window frame), analyzes their underlying ViewRootImpl mechanisms with code examples, and demonstrates practical demos showing how each mode affects layout and scrolling behavior.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Understanding Android SOFT_INPUT_ADJUST Modes: Principles, Implementation and Usage

The article begins by listing the five soft‑input adjustment constants used in Android: SOFT_INPUT_ADJUST_RESIZE , SOFT_INPUT_ADJUST_PAN , SOFT_INPUT_ADJUST_UNSPECIFIED , SOFT_INPUT_ADJUST_NOTHING and the utility method getWindowVisibleDisplayFrame(Rect outRect) . For each constant the author explains its purpose and how it influences the window when the soft keyboard appears.

Demo 1 – RESIZE

First a layout file is shown where android:windowSoftInputMode="adjustResize" is set. The XML layout contains a LinearLayout with an ImageView (fixed height) and an EditText . When the keyboard is displayed the UI does not move because the ImageView height is fixed, so the RESIZE mode appears ineffective.

The second demo modifies the ImageView height to 0dp with layout_weight="1" . After the change the parent layout height shrinks when the keyboard appears, causing the EditText to be pushed upward, which demonstrates the true effect of RESIZE .

Root Cause Analysis

The author traces the behavior to ViewRootImpl . When the keyboard pops up, the window manager sends a MSG_RESIZED message containing several Rect arguments (arg1‑arg6). arg1 is the full screen size, arg2 the content insets, arg3 the visible insets, and arg6 the stable insets (status‑ and navigation‑bar heights). The RESIZE mode changes arg2 and arg3 , which later become mPendingContentInsets and mPendingVisibleInsets inside ViewRootImpl .

#ViewRootImpl.java
final class ViewRootHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            // ...
            case MSG_RESIZED: {
                SomeArgs args = (SomeArgs) msg.obj;
                // args.arg1 -> window size
                // args.arg2 -> content insets
                // args.arg3 -> visible insets
                // args.arg6 -> stable insets
                // ... compare with previous values ...
                break;
            }
            case MSG_RESIZED_REPORT: {
                // update the stored Rects and request layout
                setFrame((Rect) args.arg1);
                mPendingContentInsets.set((Rect) args.arg2);
                mPendingVisibleInsets.set((Rect) args.arg3);
                // ...
                forceLayout(mView);
                requestLayout();
                break;
            }
        }
    }
}

When RESIZE is active, dispatchApplyInsets() is called, which updates the padding of the decor view’s child LinearLayout . This padding change propagates down the view hierarchy, ultimately altering the height of the activity’s root layout.

Pan Mode

In SOFT_INPUT_ADJUST_PAN the content insets ( arg2 ) stay unchanged, so dispatchApplyInsets() is not triggered. Instead, the system calculates the rectangle of the currently focused view and, if it is obscured by the keyboard, scrolls the root view using View.scrollToRectOrFocus() . The scrolling is performed by Scroller and applied as a Y‑offset to the canvas during the draw phase.

#View.java
boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
    Rect ci = mAttachInfo.mContentInsets;
    Rect vi = mAttachInfo.mVisibleInsets;
    int scrollY = 0;
    boolean handled = false;
    if (vi.left > ci.left || vi.top > ci.top || vi.right > ci.right || vi.bottom > ci.bottom) {
        View focus = mView.findFocus();
        if (focus != null && focus.getGlobalVisibleRect(mVisRect, null)) {
            if (mVisRect.bottom > (mView.getHeight() - vi.bottom)) {
                scrollY = mVisRect.bottom - (mView.getHeight() - vi.bottom);
                handled = true;
            }
        }
    }
    if (scrollY != mScrollY) {
        if (!immediate) {
            if (mScroller == null) mScroller = new Scroller(mView.getContext());
            mScroller.startScroll(0, mScrollY, 0, scrollY - mScrollY);
        } else if (mScroller != null) {
            mScroller.abortAnimation();
        }
        mScrollY = scrollY;
    }
    return handled;
}

During drawing, the computed curScrollY is added to the canvas Y‑offset, moving the whole window upward until the focused view becomes visible.

#ViewRootImpl.java
private boolean draw(boolean fullRedrawNeeded) {
    boolean animating = mScroller != null && mScroller.computeScrollOffset();
    int curScrollY = animating ? mScroller.getCurrY() : mScrollY;
    int xOffset = -mCanvasOffsetX;
    int yOffset = -mCanvasOffsetY + curScrollY;
    // hardware or software rendering receives (xOffset, yOffset)
    // which translates the canvas accordingly
    ...
}

Unspecified Mode

If no explicit mode is set, the system treats it as UNSPECIFIED . At runtime ViewRootImpl.performTraversals() checks whether any view in the window is marked as a scroll container ( android:isScrollContainer="true" or View.setScrollContainer(true) ). If such a view exists, the system chooses RESIZE ; otherwise it falls back to PAN .

#ViewRootImpl.java
if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
    for (int i = 0; i < mAttachInfo.mScrollContainers.size(); i++) {
        if (mAttachInfo.mScrollContainers.get(i).isShown()) {
            resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
            break;
        }
    }
    if (resizeMode == 0) {
        resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
    }
    // apply the chosen mode to the window params
}

Nothing Mode

SOFT_INPUT_ADJUST_NOTHING disables all window‑size change notifications, so the UI never moves or resizes when the keyboard appears.

Obtaining the Visible Window Frame

The method getWindowVisibleDisplayFrame(Rect outRect) returns the screen rectangle that is actually visible to the user. Internally it queries the window manager for the full display frame, then subtracts mAttachInfo.mVisibleInsets (the same insets used by RESIZE and PAN ) to produce the final outRect . This rectangle can be used to compute the keyboard height without reflection.

public void getWindowVisibleDisplayFrame(Rect outRect) {
    if (mAttachInfo != null) {
        try {
            mAttachInfo.mSession.getDisplayFrame(mAttachInfo.mWindow, outRect);
        } catch (RemoteException e) { return; }
        Rect insets = mAttachInfo.mVisibleInsets;
        outRect.left   += insets.left;
        outRect.top    += insets.top;
        outRect.right  -= insets.right;
        outRect.bottom -= insets.bottom;
    }
}

In summary, RESIZE changes layout heights via inset updates, PAN scrolls the root view to keep the focused widget visible, UNSPECIFIED selects the appropriate mode based on scroll‑container presence, and NOTHING disables any adjustment.

layoutAndroidViewRootImplResizesoft keyboardPansoftInputMode
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.