The use and principle of Android soft keyboard windowSoftInputMode (principle)

foreword

The previous article on the use and principle of Android soft keyboard windowSoftInputMode introduced the specific functions of each attribute of windowSoftInputMode. Next, we will analyze the implementation principle of each attribute at the source code level (based on Android9).

1. The principle of visibility

To analyze the hiding and display of the soft keyboard, we must first understand what the soft keyboard architecture of the Android system is like, see the figure below:
insert image description here
InputMethodManagerService (hereinafter also referred to as IMMS) is responsible for managing all input methods of the system, including input method service (InputMethodService referred to as IMS) loading and switching. When the program gets the focus, it will notify the InputMethodManagerService that it has got the focus through the InputMethodManager and request to bind itself to the current input method. At the same time, when a view of the program that needs an input method, such as EditorView, gets the focus, it will request the InputMethodManagerService to display the input method through the InputMethodManager. After receiving the request, the InputMethodManagerService will send the requested EditText data communication interface to the current input method and request to display the input method. After the input method receives the request, it displays its own UI dialog and saves the data structure of the target view at the same time. After the user completes the input, the character is directly passed to the corresponding View through the data communication interface of the view.

In addition, we also need to understand the Android window management system, see the following picture:
insert image description here

WindowManagerService (hereinafter also referred to as WMS) is very complex and is responsible for managing all windows in the system. We only need to know that when the window changes (focus state and size), it will notify the app process.

When the window of the app, that is, the Focus state of the page changes (for example: enter a new window, the Focus state of this window becomes true, and the previous window becomes false), the event will be called back to the WindowInputEventReceiver of ViewRootImpl. After a series of calls, it will enter the startInputOrWindowGainedFocusInternalLocked method of IMMS. Take a look at the core code of this method:

    @NonNull
    private InputBindResult startInputOrWindowGainedFocusInternalLocked(
            @StartInputReason int startInputReason, IInputMethodClient client,
            @NonNull IBinder windowToken, @StartInputFlags int startInputFlags,
            @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute,
            IInputContext inputContext, @MissingMethodFlags int missingMethods,
            int unverifiedTargetSdkVersion, @UserIdInt int userId) {
    
    
            
        ......省略........
        
        InputBindResult res = null;
        switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) {
    
    
            case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
                if (!isTextEditor || !doAutoShow) {
    
    
                    if (LayoutParams.mayUseInputMethod(windowFlags)) {
    
    
                        // There is no focus view, and this window will
                        // be behind any soft input window, so hide the
                        // soft input window if it is shown.
                        if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
                        hideCurrentInputLocked(
                                mCurFocusedWindow, InputMethodManager.HIDE_NOT_ALWAYS, null,
                                SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);

                        // If focused display changed, we should unbind current method
                        // to make app window in previous display relayout after Ime
                        // window token removed.
                        // Note that we can trust client's display ID as long as it matches
                        // to the display ID obtained from the window.
                        if (cs.selfReportedDisplayId != mCurTokenDisplayId) {
    
    
                            unbindCurrentMethodLocked();
                        }
                    }
                } else if (isTextEditor && doAutoShow
                        && (softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
    
    
                    // There is a focus view, and we are navigating forward
                    // into the window, so show the input window for the user.
                    // We only do this automatically if the window can resize
                    // to accommodate the IME (so what the user sees will give
                    // them good context without input information being obscured
                    // by the IME) or if running on a large screen where there
                    // is more room for the target window + IME.
                    if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
                    if (attribute != null) {
    
    
                        res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                attribute, startInputFlags, startInputReason);
                        didStart = true;
                    }
                    showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
                            SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
                }
                break;
            case LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
                // Do nothing.
                break;
            case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
                if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
    
    
                    if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
                    hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                            SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
                }
                break;
            case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
                if (DEBUG) Slog.v(TAG, "Window asks to hide input");
                hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                        SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
                break;
            case LayoutParams.SOFT_INPUT_STATE_VISIBLE:
                if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
    
    
                    if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
                    if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
                            unverifiedTargetSdkVersion, startInputFlags)) {
    
    
                        if (attribute != null) {
    
    
                            res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                    attribute, startInputFlags, startInputReason);
                            didStart = true;
                        }
                        showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
                                SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
                    } else {
    
    
                        Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
                                + " there is no focused view that also returns true from"
                                + " View#onCheckIsTextEditor()");
                    }
                }
                break;
            case LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
                if (DEBUG) Slog.v(TAG, "Window asks to always show input");
                if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
                        unverifiedTargetSdkVersion, startInputFlags)) {
    
    
                    if (attribute != null) {
    
    
                        res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                attribute, startInputFlags, startInputReason);
                        didStart = true;
                    }
                    showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
                            SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
                } else {
    
    
                    Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
                            + " there is no focused view that also returns true from"
                            + " View#onCheckIsTextEditor()");
                }
                break;
        }

You can see that the six stateXXX of windowSoftInputMode are processed here. When the focus state of the window becomes true (whether it is forward or backward), it will go to this. Let's analyze them one by one:

1.stateUnchanged

Preserves whatever state the soft keyboard was last in when the Activity comes to the foreground, whether visible or hidden.

case LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
                // Do nothing.
                break;

It can be seen that the reserved state here is Do nothing, and no operation is performed. It was displayed before, and hidden is hidden.

2.stateHidden和stateAlwaysHidden

When the user selects an activity—that is, when the user actually navigates forward into the activity, rather than returning from leaving another activity—hide the soft keyboard.

 case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
                if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
    
    
                    if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
                    hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                            SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV);
                }
                break;
 case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
                if (DEBUG) Slog.v(TAG, "Window asks to hide input");
                hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                        SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE);
                break;

From the code point of view, stateHidden is only one more judgment of LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION than stateAlwaysHidden. LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION indicates that the window is the focus obtained by moving forward. Simply put, it is to jump to a new page instead of returning to the current page. stateHidden will only call hideCurrentInputL when jumping forward. The ocked method hides the input method, but stateAlwaysHidden does not have this judgment. In any case, the window gets the focus and will hide the input method.

3.stateVisible和stateAlwaysVisible

When the user selects an activity—that is, when the user actually navigates forward into the activity, rather than returning from leaving another activity—display the soft keyboard.
Always show the soft keyboard when the Activity's main window has input focus.

case LayoutParams.SOFT_INPUT_STATE_VISIBLE:
                if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
    
    
                    if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
                    if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
                            unverifiedTargetSdkVersion, startInputFlags)) {
    
    
                        if (attribute != null) {
    
    
                            res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                    attribute, startInputFlags, startInputReason);
                            didStart = true;
                        }
                        showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
                                SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
                    } else {
    
    
                        Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
                                + " there is no focused view that also returns true from"
                                + " View#onCheckIsTextEditor()");
                    }
                }
                break;
 case LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
                if (DEBUG) Slog.v(TAG, "Window asks to always show input");
                if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
                        unverifiedTargetSdkVersion, startInputFlags)) {
    
    
                    if (attribute != null) {
    
    
                        res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                attribute, startInputFlags, startInputReason);
                        didStart = true;
                    }
                    showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
                            SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
                } else {
    
    
                    Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
                            + " there is no focused view that also returns true from"
                            + " View#onCheckIsTextEditor()");
                }
                break;

Like hiding, displaying is the same routine, so I won’t say more here.
When it is displayed, it will call the isSoftInputModeStateVisibleAllowed method to judge that the display is allowed. Take a look at this method:

    static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
            @StartInputFlags int startInputFlags) {
    
    
        if (targetSdkVersion < Build.VERSION_CODES.P) {
    
    
            // for compatibility.
            return true;
        }
        if ((startInputFlags & StartInputFlags.VIEW_HAS_FOCUS) == 0) {
    
    
            return false;
        }
        if ((startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0) {
    
    
            return false;
        }
        return true;
    }

Below Android 9, it will directly return true, and above Android 9, two judgments are added. Here is an explanation of why the soft keyboard cannot pop up automatically after Android 9. For details, please refer to the previous article.
After entering the judgment, it will judge whether the input method process is enabled. If it is not enabled, it will call startInputUncheckedLocked to enable it. After enabling it, it will remove the soft keyboard displayed with showCurrentInputLocked.

4.stateUnspecified

The state of the soft keyboard (hidden or visible) is not specified. It will be up to the system to choose the appropriate state, or rely on the setting in the theme.
The system chooses to display or hide by itself, so I won’t introduce it here, and those who are interested can study it by themselves.

case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
                if (!isTextEditor || !doAutoShow) {
    
    
                    if (LayoutParams.mayUseInputMethod(windowFlags)) {
    
    
                        // There is no focus view, and this window will
                        // be behind any soft input window, so hide the
                        // soft input window if it is shown.
                        if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
                        hideCurrentInputLocked(
                                mCurFocusedWindow, InputMethodManager.HIDE_NOT_ALWAYS, null,
                                SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);

                        // If focused display changed, we should unbind current method
                        // to make app window in previous display relayout after Ime
                        // window token removed.
                        // Note that we can trust client's display ID as long as it matches
                        // to the display ID obtained from the window.
                        if (cs.selfReportedDisplayId != mCurTokenDisplayId) {
    
    
                            unbindCurrentMethodLocked();
                        }
                    }
                } else if (isTextEditor && doAutoShow
                        && (softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
    
    
                    // There is a focus view, and we are navigating forward
                    // into the window, so show the input window for the user.
                    // We only do this automatically if the window can resize
                    // to accommodate the IME (so what the user sees will give
                    // them good context without input information being obscured
                    // by the IME) or if running on a large screen where there
                    // is more room for the target window + IME.
                    if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
                    if (attribute != null) {
    
    
                        res = startInputUncheckedLocked(cs, inputContext, missingMethods,
                                attribute, startInputFlags, startInputReason);
                        didStart = true;
                    }
                    showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
                            SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
                }
                break;

2. The principle of display mode

The display method is more complicated and involves many aspects. First of all, in the layoutWindowLw method of PhoneWindowManager (belonging to the system process), this method is mainly to set some position and size parameters of the window. When the soft keyboard pops up or is closed, it will go here to reset the size of the window. Take a look at my simplified related code:

public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
    
    

        final int type = attrs.type;
        final int fl = PolicyControl.getWindowFlags(win, attrs);
        final int pfl = attrs.privateFlags;
        final int sim = attrs.softInputMode;
        final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(null, attrs);
        final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs);

        final Rect pf = mTmpParentFrame;
        final Rect df = mTmpDisplayFrame;
        final Rect of = mTmpOverscanFrame;
        final Rect cf = mTmpContentFrame;
        final Rect vf = mTmpVisibleFrame;
        final Rect dcf = mTmpDecorFrame;
        final Rect sf = mTmpStableFrame;
        Rect osf = null;
        dcf.setEmpty();

        final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
                && mNavigationBar != null && mNavigationBar.isVisibleLw());

        final int adjust = sim & SOFT_INPUT_MASK_ADJUST;

        final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;

        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
        final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;

        sf.set(displayFrames.mStable);

        if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
    
    
            cf.set(displayFrames.mDock);
        } else {
    
    
            cf.set(displayFrames.mContent);
        }
        applyStableConstraints(sysUiFl, fl, cf, displayFrames);
        if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
    
    
            vf.set(displayFrames.mCurrent);
        } else {
    
    
            vf.set(cf);
        }
    }

First of all, there are many parameters of Rect type in the method assigned by mTmpXXXFrame. We know that Rect is used to represent a rectangular area, which is used to represent the size of the window. We need to pay attention to Rect cf = mTmpContentFrame and Rect vf = mTmpVisibleFrame; for these two parameters, cf represents the size of the window content area (which can be understood as the area available for developers), and vf represents the size of the visible area of ​​the window; when the input method pops up or collapses according to The size here will change depending on the set windowSoftInputMode, and finally the window size change will be called back to the ViewRootImpl of the application process, and finally to the MSG_RESIZED event in the ViewRootHandler .

1.adjustResize

According to the above code, we found that when SOFT_INPUT_ADJUST_RESIZE is set, cf (content area) will be set to displayFrames.mContent (display area) , and vf (visible area) will be set to displayFrames.mCurrent . Note that there is a place that is easy to confuse. cf and displayFrames.mContent express different things. When the soft keyboard pops up, we can understand that the display area of ​​the app window becomes smaller, but the content area does not Smaller, although it is covered by the soft keyboard, the content area is still usable.
Here, cf is set to displayFrames.mContent, which means that the place covered by the soft keyboard should not be used. Finally, this cf will be called back to MSG_RESIZED. Let's take a look at the code:

    public void handleMessage(Message msg) {
    
    
        switch (msg.what) {
    
    
            ........
            case MSG_RESIZED: {
    
    
                // Recycled in the fall through...
                SomeArgs args = (SomeArgs) msg.obj;
                if (mWinFrame.equals(args.arg1)
                        && mPendingOverscanInsets.equals(args.arg5)
                        && mPendingContentInsets.equals(args.arg2)
                        && mPendingStableInsets.equals(args.arg6)
                        && mPendingDisplayCutout.get().equals(args.arg9)
                        && mPendingVisibleInsets.equals(args.arg3)
                        && mPendingOutsets.equals(args.arg7)
                        && mPendingBackDropFrame.equals(args.arg8)
                        && args.arg4 == null
                        && args.argi1 == 0
                        && mDisplay.getDisplayId() == args.argi3) {
    
    
                    break;
                }
            } // fall through...
            case MSG_RESIZED_REPORT:
                if (mAdded) {
    
    
                    SomeArgs args = (SomeArgs) msg.obj;

                    final int displayId = args.argi3;
                    MergedConfiguration mergedConfiguration = (MergedConfiguration) args.arg4;
                    final boolean displayChanged = mDisplay.getDisplayId() != displayId;

                    if (!mLastReportedMergedConfiguration.equals(mergedConfiguration)) {
    
    
                        // If configuration changed - notify about that and, maybe,
                        // about move to display.
                        performConfigurationChange(mergedConfiguration, false /* force */,
                                displayChanged
                                        ? displayId : INVALID_DISPLAY /* same display */);
                    } else if (displayChanged) {
    
    
                        // Moved to display without config change - report last applied one.
                        onMovedToDisplay(displayId, mLastConfigurationFromResources);
                    }

                    final boolean framesChanged = !mWinFrame.equals(args.arg1)
                            || !mPendingOverscanInsets.equals(args.arg5)
                            || !mPendingContentInsets.equals(args.arg2)
                            || !mPendingStableInsets.equals(args.arg6)
                            || !mPendingDisplayCutout.get().equals(args.arg9)
                            || !mPendingVisibleInsets.equals(args.arg3)
                            || !mPendingOutsets.equals(args.arg7);

                    mWinFrame.set((Rect) args.arg1);
                    mPendingOverscanInsets.set((Rect) args.arg5);
                    mPendingContentInsets.set((Rect) args.arg2);
                    mPendingStableInsets.set((Rect) args.arg6);
                    mPendingDisplayCutout.set((DisplayCutout) args.arg9);
                    mPendingVisibleInsets.set((Rect) args.arg3);
                    mPendingOutsets.set((Rect) args.arg7);
                    mPendingBackDropFrame.set((Rect) args.arg8);
                    mForceNextWindowRelayout = args.argi1 != 0;
                    mPendingAlwaysConsumeNavBar = args.argi2 != 0;

                    args.recycle();

                    if (msg.what == MSG_RESIZED_REPORT) {
    
    
                        reportNextDraw();
                    }

                    if (mView != null && framesChanged) {
    
    
                        forceLayout(mView);
                    }
                    requestLayout();
                }
                break;
            .........
        }
    }

args.arg2 in SomeArgs is the cf we set before.
1. After entering MSG_RESIZED, it will judge whether the parameters in SomeArgs are the same as before. If it is different, it will go to MSG_RESIZED_REPORT. 2. After saving the
size information here, it will call forceLayout(mView)—>mark layout and draw for each View/ViewGroup in ViewTree, which means that each View/ViewGroup will execute three major processes at the end. Finally, requestLayout()—> triggers the execution of three major processes.

Now that the size changes are documented, keep track of how these values ​​are used. Calling requestLayout() will trigger the execution of the performTraversals() method:

#ViewRootImpl.java
    private void performTraversals() {
    
    
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
    
    
            ...
            boolean hwInitialized = false;
            //内容边界是否发生变化
            boolean contentInsetsChanged = false;
            try {
    
    
                ...
                //内容区域变化----------->1
                contentInsetsChanged = !mPendingContentInsets.equals(
                        mAttachInfo.mContentInsets);

                if (contentInsetsChanged || mLastSystemUiVisibility !=
                        mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
                        || mLastOverscanRequested != mAttachInfo.mOverscanRequested
                        || outsetsChanged) {
    
    
                    ...
                    //分发Inset----------->2
                    dispatchApplyInsets(host);
                    contentInsetsChanged = true;
                }
                ...
            } catch (RemoteException e) {
    
    
            }
            ...
        }
        ...
    }

1. When SOFT_INPUT_ADJUST_RESIZE is set, the content area changes when the keyboard pops up, so dispatchApplyInsets() will be executed.
2. Distribute Insets.
The values ​​of these records will be stored in the variables corresponding to AttachInfo.
The call stack of this method is as follows:
insert image description here
The insets in dispatchApplyWindowInsets(WindowInsets insets) are composed of the boundary values ​​recorded in mPendingXX before calculation.
Finally call fitSystemWindowsInt():

#View.java
    private boolean fitSystemWindowsInt(Rect insets) {
    
    
        //FITS_SYSTEM_WINDOWS 为xml里设置     android:fitsSystemWindows="true"
        //对于DecorView的子布局LinearLayout来说,默认fitsSystemWindows=true
        if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
    
    
            ...
            //设置View的padding
            internalSetPadding(localInsets.left, localInsets.top,
                    localInsets.right, localInsets.bottom);
            return res;
        }
        return false;
    }

    protected void internalSetPadding(int left, int top, int right, int bottom) {
    
    
        ...
        if (mPaddingLeft != left) {
    
    
            changed = true;
            mPaddingLeft = left;
        }
        if (mPaddingTop != top) {
    
    
            changed = true;
            mPaddingTop = top;
        }
        if (mPaddingRight != right) {
    
    
            changed = true;
            mPaddingRight = right;
        }
        if (mPaddingBottom != bottom) {
    
    
            changed = true;
            mPaddingBottom = bottom;
        }

        if (changed) {
    
    
            requestLayout();
            invalidateOutline();
        }
    }

Seeing this answer, the answer is ready to come out. The sub-layout LinearLayout of DecorView sets the padding, which will eventually affect the height of the LinearLayout sub-layout. If it is passed down layer by layer, it will affect the height of the Activity layout file in the Demo.

Summary
When setting SOFT_INPUT_ADJUST_RESIZE, DecorView's sub-layout padding will change, and finally affect the height of descendant layout.

2.adjustPan

When SOFT_INPUT_ADJUST_PAN is set, cf (content area) will be set to displayFrames.mDock , and vf (visible area) will also be set to displayFrames.mCurrent . What is displayFrames.mDock?

    /** During layout, the current screen borders along which input method windows are placed. */
    public final Rect mDock = new Rect();

During the layout, the current screen border where the input method window is located. I understand that the app can use the content area except the navigation bar and status bar. Later, through the breakpoint verification, it is true that SOFT_INPUT_ADJUST_RESIZE sets cf as the visible area, and SOFT_INPUT_ADJUST_PAN here cf is set as the content area, which means that the app can use the part blocked by the input method.
In the end, it will also go to MSG_RESIZED:
args.arg2 in SomeArgs, that is, the size of cf has not changed, but args.arg3, that is, vf, has changed, so it will still enter MSG_RESIZED_REPORT, and will still call forceLayout(mView) and requestLayout()—>trigger the execution of the three major processes.

But since the content area has not changed, dispatchApplyInsets() will not be executed here. Where is the processing of SOFT_INPUT_ADJUST_PAN?
We know that the effect of SOFT_INPUT_ADJUST_PAN is to move the page upwards as a whole. The layout movement is nothing more than a change in coordinates or scrolling of the content. Regardless of the form, the Canvas needs to be displaced to achieve the effect of movement. There is a scrollToRectOrFocus method in the performDraw->draw method:

#View.java
    boolean scrollToRectOrFocus(Rect rectangle, boolean immediate) {
    
    
        //窗口内容区域
        final Rect ci = mAttachInfo.mContentInsets;
        //窗口可见区域
        final 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) {
    
    
            scrollY = mScrollY;
            //找到当前有焦点的View------------>(1)
            final View focus = mView.findFocus();
            ...
            if (focus == lastScrolledFocus && !mScrollMayChange && rectangle == null) {
    
    
                //焦点没有发生切换,不做操作
            } else {
    
    
                // We need to determine if the currently focused view is
                // within the visible part of the window and, if not, apply
                // a pan so it can be seen.
                mLastScrolledFocus = new WeakReference<View>(focus);
                mScrollMayChange = false;
                if (DEBUG_INPUT_RESIZE) Log.v(mTag, "Need to scroll?");
                // Try to find the rectangle from the focus view.
                if (focus.getGlobalVisibleRect(mVisRect, null)) {
    
    
                    ...
                    //找到当前焦点与可见区域的相交部分
                    //mVisRect 为当前焦点在Window里的可见部分
                    if (mTempRect.intersect(mVisRect)) {
    
    
                        if (mTempRect.height() >
                                (mView.getHeight()-vi.top-vi.bottom)) {
    
    
                            ...
                        }
                        else if (mTempRect.top < vi.top) {
    
    
                            //如果当前焦点位置在窗口可见区域上边,说明焦点View应该往下移动到可见区域里边
                            scrollY = mTempRect.top - vi.top;
                        } else if (mTempRect.bottom > (mView.getHeight()-vi.bottom)) {
    
    
                            //如果当前焦点位置在窗口可见区域之下,说明其应该往上移动到可见区域里边------->(2)
                            scrollY = mTempRect.bottom - (mView.getHeight()-vi.bottom);
                        } else {
    
    
                            //无需滚动------->(3)
                            scrollY = 0;
                        }
                        handled = true;
                    }
                }
            }
        }

        if (scrollY != mScrollY) {
    
    
            //滚动距离发生变化
            if (!immediate) {
    
    
                if (mScroller == null) {
    
    
                    mScroller = new Scroller(mView.getContext());
                }
                //开始设置滚动----------->(4)
                mScroller.startScroll(0, mScrollY, 0, scrollY-mScrollY);
            } else if (mScroller != null) {
    
    
                mScroller.abortAnimation();
            }
            //赋值给成员变量
            mScrollY = scrollY;
        }
        return handled;
    }

1. For the above Demo, the current focus View is the EditText, and which EditText is clicked will get the focus.
2. For input box 2, because the keyboard will pop up and cover it, the calculation satisfies the condition of "the current focus position is below the visible area of ​​the window, indicating that it should move up to the visible area", so srolly > 0.
3. As for input box 1, when the keyboard pops up, it is not blocked by the keyboard, and goes to the else branch, so scrollY = 0.
4. Scrolling is done with the help of the Scoller.java class.

The above operation is actually to confirm the scroll value and record it in the member variable mScrollY. Let’s continue to see how to use the scroll value?

#ViewRootImpl.java
    private boolean draw(boolean fullRedrawNeeded) {
    
    
        ...
        boolean animating = mScroller != null && mScroller.computeScrollOffset();
        final int curScrollY;
        //获取当前需要滚动的scroll值
        if (animating) {
    
    
            curScrollY = mScroller.getCurrY();
        } else {
    
    
            curScrollY = mScrollY;
        }
        ...
        
        int xOffset = -mCanvasOffsetX;
        //记录在yOffset里
        int yOffset = -mCanvasOffsetY + curScrollY;
        
        boolean useAsyncReport = false;
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
    
    
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
    
    
                ...
                //对于走硬件加速绘制
                if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
    
    
                    //记录偏移量到mHardwareYOffset里
                    mHardwareYOffset = yOffset;
                    mHardwareXOffset = xOffset;
                    invalidateRoot = true;
                }
                ..
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else {
    
    
                //软件绘制
                //传入yOffset
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                        scalingRequired, dirty, surfaceInsets)) {
    
    
                    return false;
                }
            }
        }
        ...
        return useAsyncReport;
    }

The scrolling value is passed to the hardware-accelerated drawing branch and the software drawing branch respectively, and the Canvas is translated in their respective branches. How to do the translation will not be detailed here, and you can continue to follow the code analysis.

summary

1. When SOFT_INPUT_ADJUST_PAN is set, if the keyboard is found to cover the currently focused View, the Canvas of RootView (here, DecorView in Demo is used as RootView) will be translated until the focused View is displayed in the visible area.
2. This is why the layout will move up as a whole when the input box 2 is clicked.

3.adjustNothing

When set to SOFT_INPUT_ADJUST_NOTHING, cf (content area) will be set to displayFrames.mDock , and vf (visible area) will be set to cf. In fact, this means that the window size parameter has not changed, so it will not be called back to MSG_RESIZED, and the natural layout will not change.

4.adjustUnspecified

SOFT_INPUT_ADJUST_UNSPECIFIED is set, which internally ends up using one of SOFT_INPUT_ADJUST_PAN and SOFT_INPUT_ADJUST_RESIZE for display. Next, let's explore what the selection criteria are.
When softInputMode is not set, the default is SOFT_INPUT_ADJUST_UNSPECIFIED mode.
Still start the analysis from the performTraversals() method of ViewRootImpl.java:

    private void performTraversals() {
    
    
        ...
        if (mFirst || mAttachInfo.mViewVisibilityChanged) {
    
    
            mAttachInfo.mViewVisibilityChanged = false;
            //先查看有没有提前设置了模式
            int resizeMode = mSoftInputMode &
                    WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
            
            //如果没有设置,那么默认为0,也就是SOFT_INPUT_ADJUST_UNSPECIFIED
            if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
    
    
                //查看mScrollContainers 数组有没有元素(View 作为元素) -------->(1)
                final int N = mAttachInfo.mScrollContainers.size();
                for (int i=0; i<N; i++) {
    
    
                    if (mAttachInfo.mScrollContainers.get(i).isShown()) {
    
    
                        //如果有元素,则设置为SOFT_INPUT_ADJUST_RESIZE 模式
                        resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
                    }
                }
                if (resizeMode == 0) {
    
    
                    //如果没有设置为resize模式,则设置pan模式
                    resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
                }
                if ((lp.softInputMode &
                        WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
    
    
                    lp.softInputMode = (lp.softInputMode &
                            ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
                            resizeMode;
                    //最后赋值给params,让Window属性生效
                    params = lp;
                }
            }
        }
        ...
    }

As mentioned in the previous article, here is mainly to judge whether there is a scrollable view through mAttachInfo.mScrollContainers, if there is, set it to SOFT_INPUT_ADJUST_RESIZE, if not, set it to SOFT_INPUT_ADJUST_PAN.

Guess you like

Origin blog.csdn.net/shanshui911587154/article/details/127537625