preface

Soft keyboard series of articles:

Android- a soft keyboard to fix (practice) Android- a soft keyboard to fix (Principle)

The soft keyboard is one of the most important ways for Android to interact with users, and it is almost impossible to avoid using it in Android application development. However, there is no clear API to get such things as whether the soft keyboard is being displayed, the height of the soft keyboard, etc. This article will explore solutions to this problem.

Through this article, you will learn:

1, soft keyboard open and close 2, soft keyboard interface adaptation 3, soft keyboard height acquisition

1. Open and close the soft keyboard

For convenience, a keyboard is used instead of a soft keyboard. The EditText control is the most commonly used control. When EditText is clicked, the keyboard pops up. When the bottom navigation bar is clicked, the keyboard collapses, as follows:


Keyboard pops up

Let’s take a look at how EditText pops the keyboard.

#TextView.java public boolean onTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); . final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) && (mEditor == null || ! mEditor.mIgnoreActionUpEvent) && isFocused(); if ((mMovement ! = null || onCheckIsTextEditor()) && isEnabled() && mText instanceof Spannable && mLayout ! = null) { boolean handled = false; . If (touchIsFinished && (isTextEditable () | | textIsSelectable)) {/ / get InputMethodManager final InputMethodManager imm = getInputMethodManager(); viewClicked(imm); if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm ! Imm. ShowSoftInput (this, 0); }... }... } return superResult; }Copy the code

You can see that there are only two steps required to eject the keyboard:

Get InputMethodManager instance showSoftInput(xx)

Keyboard closed

         InputMethodManager inputMethodManager = (InputMethodManager)view.getContext().getSystemService(INPUT_METHOD_SERVICE);
         inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
Copy the code

Similarly, you can see that there are only two steps to retract the keyboard:

2. Call hideSoftInputFromWindow(xx)

Pop up/close the utility class

Extract the eject/close method:

public class SoftInputUtil {
    public static void showSoftInput(View view) {
        if (view == null)
            return;
        InputMethodManager inputMethodManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if (inputMethodManager != null) {
            inputMethodManager.showSoftInput(view, 0);
        }
    }

    public static void hideSoftInput(View view) {
        if (view == null)
            return;
        InputMethodManager inputMethodManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if (inputMethodManager != null) {
            inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
        }
    }
}
Copy the code

There are a few things to note:

1, inputMethodManager. ShowSoftInput (xx) is generally need to View the EditText type. If you pass in other views, you need to do something extra to bring up the keyboard. 2, inputMethodManager. ShowSoftInput (xx), inputMethodManager. HideSoftInputFromWindow (xx) The last parameter of the two methods is used to match the type passed when the keyboard is closed to determine when the keyboard was popped up. HideSoftInputFromWindow (xx) The IBinder windowToken type passed in as the first parameter. Each Activity is created with a windowToken, which is stored in AttachInfo, so each View holds a windowToken that points to an object for the ViewTree within the same Activity.

In view of the above points 1 and 3, I would like to add:

Assume that when the incoming View is the Button type, you need to set up the Button. SetFocusableInTouchMode (true), at this time to the pop-up keyboard. A better way to do this is to also pop up the keyboard in onTouchEvent(xx) and associate the Button with the keyboard. The system provides EditText to receive input characters, so you don’t need to do the whole thing yourself, so you usually pass EditText when you pop up the keyboard.

For 3 points:

Because windowToken in the same ViewTree is the same, you don’t have to pass EditText, you can pass Button, etc., as long as it belongs to the same ViewTree.

The pop-up and close effects are as follows:

2, soft keyboard interface adaptation

A small Demo

Take a look at the Demo and set up the Activity layout file:

<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/myviewgroup" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="20dp" android:orientation="vertical" android:layout_gravity="center_vertical" tools:context=".MainActivity"> <ImageView android:id="@+id/iv" android:src="@drawable/test" android:layout_marginTop="20dp" android:layout_marginBottom="20dp" </ImageView> <EditText Android :hint=" input box 2" android:id="@+id/et2" android:layout_gravity="bottom" android:layout_marginTop="200dp" android:layout_width="100dp" android:layout_height="40dp"> </EditText> </LinearLayout>Copy the code

Very simple, just an ImageView and EditText arranged vertically in a LinearLayout. When you click EditText, it looks like this:





1. When the keyboard pops up, you just want the EditText to top up, and the ImageView to stay put. 2

First of all, we know that the soft keyboard is actually a Dialog. When the keyboard is up, we can actually see two Windows: 1 is the Window of the Activity and 2 is the Window of the Dialog. The problem is that the Dialog Window display hides part of the Activity’s Window. In order for the EditText to be visible, the Activity’s layout is pushed up, assuming that the Window property has parameters that control whether it is pushed up or not. Return true have, this parameter is the WindowManager. LayoutParams. SoftInputMode.

WindowManager.LayoutParams.softInputMode

SoftInputMode Indicates the mode of a soft keyboard. It controls whether the keyboard is visible, whether the EditText associated with the keyboard moves with the keyboard, and so on. We focus on the following properties:

#WindowManager.java
public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
Copy the code

The functions of the preceding values are described as follows:

SOFT_INPUT_ADJUST_UNSPECIFIED The adjustment mode is not specified. The system determines the adjustment mode

SOFT_INPUT_ADJUST_RESIZE Indicates that the adjust_size needs to be adjusted for the visible area

SOFT_INPUT_ADJUST_PAN Indicates that the layout needs to be adjusted as a whole

SOFT_INPUT_ADJUST_NOTHING Does nothing

There are two ways to set softInputMode: 1. Code setting: Get the Window object and set it

getWindow().getAttributes().softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
Copy the code

2. Set the Activity attribute in androidmanifest.xml

android:windowSoftInputMode="adjustResize"
Copy the code

See what the values look like: SOFT_INPUT_ADJUST_UNSPECIFIED




The layout is jacked up

SOFT_INPUT_ADJUST_PAN




The layout is jacked up

SOFT_INPUT_ADJUST_RESIZE




The layout is not moving

SOFT_INPUT_ADJUST_NOTHING




The layout is not moving

By looking at the four pictures above, you might be wondering: SOFT_INPUT_ADJUST_UNSPECIFIED has the same effect as SOFT_INPUT_ADJUST_PAN. SOFT_INPUT_ADJUST_RESIZE has the same effect as SOFT_INPUT_ADJUST_NOTHING. For example, SOFT_INPUT_ADJUST_PAN (), SOFT_INPUT_ADJUST_NOTHING (), makes adjust_pan (). The value of SOFT_INPUT_ADJUST_RESIZE and SOFT_INPUT_ADJUST_UNSPECIFIED are unknown.

Adjust_resize SOFT_INPUT_ADJUST_RESIZE

<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/myviewgroup" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="20dp" android:orientation="vertical" android:layout_gravity="center_vertical" tools:context=".MainActivity"> <ImageView android:id="@+id/iv" android:src="@drawable/test" android:layout_marginTop="20dp" android:layout_marginBottom="20dp" android:scaleType="fitXY" android:layout_weight="1" android:layout_width="match_parent" android:layout_height="0dp"> </ImageView> <EditText Android :hint=" "Android :id="@+id/et2" Android :layout_gravity="bottom" android:layout_marginTop="10dp" android:layout_width="100dp" android:layout_height="40dp"> </EditText> </LinearLayout>Copy the code

In fact, the above layout simply expands the ImageView display. In SOFT_INPUT_ADJUST_RESIZE mode, after adjust_resize, it works like this:

<div align=center>! [device-2020-10-14-213218.gif](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a99ef99841944b4a99f40bc8a8b9499b~tplv-k 3u1fbpfcp-zoom-1.image)</div>Copy the code

As you can see, the ImageView is compressed, indicating that the layout file has been recalculated. Why is the effect different before and after changing the layout? The answer will be found in the next chapter, Principles.

SOFT_INPUT_ADJUST_UNSPECIFIED Change the Activity layout file and add isScrollContainer to the ImageView

android:isScrollContainer="true"
Copy the code

In SOFT_INPUT_ADJUST_UNSPECIFIED mode, the value is as follows:

The value of SOFT_INPUT_ADJUST_UNSPECIFIED mode can be the same as that of SOFT_INPUT_ADJUST_PAN or Adjust_Resize.

When the SOFT_INPUT_ADJUST_UNSPECIFIED mode is used, the SOFT_INPUT_ADJUST_RESIZE or SOFT_INPUT_ADJUST_PAN mode is used to show this mode.

By experimenting with these four values, let’s look at the two questions raised in the previous Demo:

When the keyboard is popping up, I don’t need any View to be popping up. Adjust_nothing (SOFT_INPUT_ADJUST_NOTHING

3, soft keyboard height acquisition

For question 1 above, if you want the EditText to top alone, you need to know the current keyboard pop-up height and set the EditText coordinates. The key turned out to be how to get the height of the keyboard.

The composition of the Activity window

<div align=center>! [image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b610a3ad8bdf434a8f576010a7dcdd1b~tplv-k3u1fbpfcp-zoom-1.im age) </div>Copy the code

Typically, a mobile phone consists of a status bar, a content area, and a navigation bar. In general (navigation bar hiding aside, status bar immersion) for us, written layout files are displayed in the content area, which is “visible”. When the keyboard pops up, it covers part of the content area:

So, you have to move the part that’s covered up, by how much? By calling the method:

#View.java
    public void getWindowVisibleDisplayFrame(Rect outRect) {
        ...
    }
Copy the code

The position of the visible area is recorded in outRect, and the total screen height is known, so the offset that the shaded area needs to be jacked up can be calculated.

A general calculation

According to the above analysis, the calculation method is encapsulated as follows:

public class SoftInputUtil { private int softInputHeight = 0; private boolean softInputHeightChanged = false; private boolean isNavigationBarShow = false; private int navigationHeight = 0; private View anyView; private ISoftInputChanged listener; private boolean isSoftInputShowing = false; public interface ISoftInputChanged { void onChanged(boolean isSoftInputShow, int softInputHeight, int viewOffset); } public void attachSoftInput(final View anyView, final ISoftInputChanged listener) { if (anyView == null || listener == null) return; // Root final View rootView = anyView.getrootView (); if (rootView == null) return; navigationHeight = getNavigationBarHeight(anyView.getContext()); AnyView = anyView; anyView = anyView; anyView = anyView; this.listener = listener; rootView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, Int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {// For Activity, Int rootHeight = rootView.getheight (); Rect rect = new Rect(); / / get the current visible part, the default is visible part in addition to the status bar and the rest of the navigation bar rootView. GetWindowVisibleDisplayFrame (the rect); If (rootHeight - rect.bottom == navigationHeight) {// If (rootHeight - rect.bottom == navigationHeight) {// If (rootHeight - rect.bottom == navigationHeight) {// If (rootHeight - rect. } else if (rootHeight - rect.bottom == 0) {// If the visible part of the bottom is flush with the bottom of the screen, there is no navigation bar isNavigationBarShow = false; } //cal softInput height boolean isSoftInputShow = false; int softInputHeight = 0; Int mutableHeight = isNavigationBarShow == true? navigationHeight : 0; If (rootHeight mutableHeight - > the rect. Bottom) {/ / after removal of the navigation bar height, visible region is still less than the height of the screen, then the keyboard pop-up isSoftInputShow = true; // keyboard height softInputHeight = rootHeight - mutableheight-rect. bottom; if (SoftInputUtils.this.softInputHeight ! = softInputHeight) { softInputHeightChanged = true; SoftInputUtils.this.softInputHeight = softInputHeight; } else { softInputHeightChanged = false; Int [] location = new int[2]; anyView.getLocationOnScreen(location); // Condition 2: If (isSoftInputShowing! = isSoftInputShow || (isSoftInputShow && softInputHeightChanged)) { if (listener ! = null) {listener.onChanged(isSoftInputShow, softInputHeight) {listener.onChanged(isSoftInputShow, softInputHeight, location[1] + anyView.getHeight() - rect.bottom); } isSoftInputShowing = isSoftInputShow; }}}); } //***************STATIC METHOD****************** public static int getNavigationBarHeight(Context context) { if (context == null) return 0; Resources resources = context.getResources(); int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); int height = resources.getDimensionPixelSize(resourceId); return height; } public static void showSoftInput(View view) { if (view == null) return; InputMethodManager inputMethodManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager ! = null) { inputMethodManager.showSoftInput(view, 0); } } public static void hideSoftInput(View view) { if (view == null) return; InputMethodManager inputMethodManager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager ! = null) { inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); }}}Copy the code

Add the following code to your Activity:

Private void attachView() {//editText2 = findViewById(R.id.t2); SoftInputUtil softInputUtil = new SoftInputUtil(); softInputUtil.attachSoftInput(editText2, new SoftInputUtil.ISoftInputChanged() { @Override public void onChanged(boolean isSoftInputShow, int softInputHeight, int viewOffset) { if (isSoftInputShow) { editText2.setTranslationY(et2.getTranslationY() - viewOffset); } else { editText2.setTranslationY(0); }}}); }Copy the code

And set windowSoftInputMode to SOFT_INPUT_ADJUST_RESIZE.

android:windowSoftInputMode="adjustResize|stateAlwaysHidden"
Copy the code

StateAlwaysHidden does not display the keyboard by default.

Look again at the Activity layout file:

<? The XML version = "1.0" encoding = "utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/myviewgroup" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:layout_gravity="center_vertical" tools:context=".MainActivity"> <ImageView android:id="@+id/iv" android:src="@drawable/test" android:background="@color/colorGreen" android:layout_marginTop="20dp" android:layout_marginBottom="20dp" </ImageView> <EditText Android :hint=" input box 2" android:id="@+id/et2" android:layout_marginTop="100dp" android:background="@drawable/bg" android:layout_gravity="bottom"  android:layout_marginHorizontal="10dp" android:layout_width="match_parent" android:layout_height="40dp"> </EditText> </LinearLayout>Copy the code

The final effect is as follows:

As you can see, the EditText is topped up, leaving the rest of the layout unchanged. EeditText also works between dynamically toggled whether to display the navigation bar. This answers question 1 above: When the keyboard pops up, you just want the EditText to go up, and the ImageView to stay put. Of course, there are other, simpler solutions to problem 1, which will be examined in the next article.

The above is about the soft keyboard pop-up, close, whether to show, soft keyboard height, soft keyboard mode and other effects of the analysis. You may also have the following doubts:

Why SOFT_INPUT_ADJUST_PAN can make the layout top up? SOFT_INPUT_ADJUST_RESIZE Why can I reset the layout area? SOFT_INPUT_ADJUST_UNSPECIFIED How can I determine the internal logic? Why does onLayout execute when the keyboard is up? .

Due to space constraints, these issues will be analyzed in the next part: Android Soft Keyboard one-trick solution (Principles).

This article is based on Android 10.0.

If you need it, please copy softinpututil.java from the article and try to control the keyboard. If you have any questions, please leave a message, thank you!

If you like, please like, your encouragement is my motivation to move forward.

Continue to update, with me step by step system, in-depth study of Android/Java