Recently, I encountered a bug in the development of a wechat product: soft keyboard mask. In the process of solving the bug, I searched baidu and Google for quite a long time, and finally experienced a bumpy solution. Let me explain the specific process in detail.

A, windowSoftInputMode

This is in the soft keyboard related problems, the brain of the first knowledge point, but the effect of how? It works, but it’s not perfect! First look at the renderings that have not been solved:

This means that when the last item in a list needs to be typed, the soft keyboard completely masks the EditText! In fact, this effect can also be compromised, but we can choose not to compromise – dead kowtowing! So let’s see how does Windows Of PutMode solve this problem?

After the Activity gets focus, the soft keyboard hides and displays
stateUnspecified Does not specify the state of the soft keyboard (Hidden or visible) it is up to the system to select the appropriate state, or to rely on Settings in the theme, which is the default setting for soft keyboard behavior
stateUnchanged Keep state Retain any final state of the soft keyboard when the Activity comes to the foreground, whether visible or hidden
stateHidden Hidden soft keyboard Hide the soft keyboard when the user actually navigates forward to the Activity, rather than returning from another Activity
stateAlwaysHidden Always hide the soft keyboard Always hide the soft keyboard when the Activity’s main window has an input focus
stateVisible Display soft keyboard Displays the soft keyboard under normal, appropriate circumstances (when the user navigates forward to the main window of the Activity)
stateAlwaysVisible Always display soft keyboard The soft keyboard is displayed when the user actually navigates forward to the Activity, rather than returning from leaving another Activity
When the soft keyboard pops up, the Activity adjusts its strategy
adjustUnspecified The default behavior of the main window, which does not specify whether the Activity’s main window is resized to make room for the soft keyboard, or whether the window content is panned to reveal the current focus on the screen. The system automatically selects one of these modes based on whether there are any layout views for scrolling the contents of the window. If such a view exists, the window will be resized, provided that the entire contents of the window can be seen in a small area by scrolling.
adjustResize Always resize the Activity’s main window to make room for the soft keyboard on the screen. When the soft keyboard pops up, it redraws the layout, which is generally appropriate for controls with a sliding nature that scroll down and then adapt to the soft keyboard display.
adjustPan Instead of resizing the Activity’s main window to make room for the soft keyboard, the content of the window is automatically panted so that the current focus is never obscured by the keyboard and the user can always see what they are typing. This is usually not as desirable as resizing, as the user may need to close the soft keyboard to reach or interact with obscured portions of the window.
adjustNoting When the soft keyboard pops up, the main window Activity does not respond.

The table above illustrates two issues: the soft keyboard display and the Activity response strategy. In the above project, there is no problem with the soft keyboard display, but part of the Activity is masked and can be adjusted to solve the problem. So let’s try each of these response strategies in turn!

  • StateUnspecified:

The default strategy covers the bottom of the soft keyboard as soon as it comes in, and we cannot operate the item at the bottom. Therefore, we need to add a strategy without displaying the soft keyboard when we come in

	android:windowSoftInputMode="stateHidden|stateUnspecified"
Copy the code

Now it is not displayed, but clicking on the bottom item will still be blocked:

  • AdjustPan:
	android:windowSoftInputMode="stateHidden|stateUnspecified"
Copy the code

AdjustPan does make the Activity main window move up, but the adjustPan part of my Title also move up! That’s what I mean by imperfect, so what if we try repainting the main window?

  • AdjustResize:
	android:windowSoftInputMode="stateHidden|adjustResize"
Copy the code

AdjustResize doesn’t work, the input interface at the bottom is still masked, SO I can only accept adjustPan. But there’s also an adjustNoting strategy to see if it’s the same? Since the dead knock, let’s try each one of them!

  • adjustNoting
	android:windowSoftInputMode="stateHidden|adjustNothing"
Copy the code

Very good, indeed did not disappoint us, is really not!

ConstraintLayout, RelativeLayout, and FrameLayout layouts that place EditText at the bottom of the layout test default to normal.

But why wechat chat page use RecyclerView layout effect is not like this ah? For this I contacted a big god of imitation circle of friends, he told me the second method: dynamic calculation of the height of the soft keyboard

Second, dynamic calculation of soft keyboard height

To dynamically calculate the height of the soft keyboard, we need to increase the distance between the soft keyboard and the EditText.

As for why it is difficult, it is not the product requirement… Since others can achieve it, we also try to achieve it! Because of the dynamic calculation of the height of the soft keyboard, we do not need to set SoftInputMode, because the whole process is purely manual operation, do not need other API support system!

  1. First of all, we need to do some preparatory work, the soft keyboard and the home page content stripping, the home page content is a RecyclerView, the soft keyboard part is a layout containing EditText, soft keyboard layout as shown in the figure:

  1. Encapsulate the soft keyboard above, this is the point. It’s a bit abstract, just like the previous flowchart and code:

Public Class EmojiPanelView extends LinearLayout implements OnKeyBoardStateListener {··· Public EmojiPanelView(Context) context) { super(context); init(); } private voidinit() {
        View itemView = LayoutInflater.from(getContext()).inflate(R.layout.view_emoji_panel, this, false);
        mEditText = itemView.findViewById(R.id.edit_text);
        mEditText.setOnTouchListener((v, event) -> {
            showSoftKeyBoard();
            return true;
        });

        mImageSwitch = itemView.findViewById(R.id.img_switch);
        mImageSwitch.setOnClickListener(v -> {
            if (isKeyBoardShow) {
                mImageSwitch.setImageResource(R.drawable.input_keyboard_drawable);
                changeLayoutNullParams(false);
                hideSoftKeyBoard();
                changeEmojiPanelParams(mKeyBoardHeight);
            } else{ mImageSwitch.setImageResource(R.drawable.input_smile_drawable); showSoftKeyBoard(); }}); ... addOnSoftKeyBoardVisibleListener ((Activity) getContext (), this); addView(itemView); } @Override public boolean onTouchEvent(MotionEvent event) {if (event.getY() < Utils.getScreenHeight() - Utils.dp2px(254f) && isShowing()) {
            dismiss();
        }
        return super.onTouchEvent(event);
    }
private void showSoftKeyBoard() {
        InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if(inputMethodManager ! = null && mEditText ! = null) { mEditText.post(() -> { mEditText.requestFocus(); inputMethodManager.showSoftInput(mEditText, 0); }); new Handler().postDelayed(() -> { changeLayoutNullParams(true);
                changeEmojiPanelParams(0);
            }, 200);
        }
    }


    private void hideSoftKeyBoard() {
        InputMethodManager inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
        if(inputMethodManager ! = null && mEditText ! = null) { inputMethodManager.hideSoftInputFromWindow(mEditText.getWindowToken(), 0); } } private void changeLayoutNullParams(boolean isShowSoftKeyBoard) { LinearLayout.LayoutParams params = (LayoutParams) mLayoutNull.getLayoutParams();if (isShowSoftKeyBoard) {
            params.weight = 1;
            params.height = 0;
            mLayoutNull.setLayoutParams(params);
        } else {
            params.weight = 0;
            params.height = mDisplayHeight;
            mLayoutNull.setLayoutParams(params);
        }
    }

    private void changeEmojiPanelParams(int keyboardHeight) {
        if(mLayoutEmojiPanel ! = null) { LinearLayout.LayoutParams params = (LayoutParams) mLayoutEmojiPanel.getLayoutParams(); params.height = keyboardHeight; mLayoutEmojiPanel.setLayoutParams(params); } } boolean isVisiableForLast =false; public void addOnSoftKeyBoardVisibleListener(Activity activity, final OnKeyBoardStateListener listener) { final View decorView = activity.getWindow().getDecorView(); decorView.getViewTreeObserver().addOnGlobalLayoutListener(() -> { Rect rect = new Rect(); decorView.getWindowVisibleDisplayFrame(rect); Int displayHight = rect.bottom-rect.top; int displayHight = rect.bottom-rect.top; Int hight = decorView.getheight (); / / get the keyboard height int keyboardHeight = hight - displayHight - Utils. CalcStatusBarHeight (getContext ()); Boolean visible = (double) displayHight/hight < 0.8;if(visible ! = isVisiableForLast) { listener.onSoftKeyBoardState(visible, keyboardHeight, displayHight - Utils.dp2px(48f)); } isVisiableForLast = visible; }); } @Override public void onSoftKeyBoardState(boolean visible, int keyboardHeight, int displayHeight) { this.isKeyBoardShow = visible;if(visible) { mKeyBoardHeight = keyboardHeight; mDisplayHeight = displayHeight; }}}Copy the code
  1. Add the custom layout to the home page content, and then we don’t have to set Windows of PutMode. Layout:
<? xml version="1.0" encoding="utf-8"? > <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.CustomActivity">

    <include
            android:id="@+id/custom_top_layout"
            layout="@layout/toolbar_layout"/>

    <android.support.v7.widget.RecyclerView
            android:id="@+id/custom_items"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintTop_toBottomOf="@+id/custom_top_layout"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
    >

    </android.support.v7.widget.RecyclerView>

    <com.sasucen.softinput.widget.EmojiPanelView
            android:id="@+id/layout_face_panel"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toBottomOf="parent">

    </com.sasucen.softinput.widget.EmojiPanelView>

</android.support.constraint.ConstraintLayout>

Copy the code

Effect:

This is still good, but most apps now have an immersive status bar, so let’s add an immersive status bar!

Oh dear, the input box is masked! Next, let’s move on to the third step – the final pit filling!

In the end filling holes


I remember going blind when I came to this place. There is no mention of the connection between the soft keyboard mask and the immersive status bar. It works when using windowSoftInputMode, but it is not ideal because the EditText has no space between the soft keyboard, as shown in the picture below.

Later, when I consulted the boss, he gave me an idea — the loss of the status bar height. Later, I tried to calculate the size of the screen visible height and the overall screen height, and both failed. EditText was completely masked! The position of the EditText is always the position of the soft keyboard mask, regardless of whether the layout increases or decreases the height of the status bar. The original plan was to set the padding of the titbar and change the color of the status bar to achieve an immersive status bar, but I couldn’t get over it! Later, I remembered an article I had read earlier, which I had memorized from the technical team’s blog about the “fitsSystemWindows” property, so I tried it:

<? xml version="1.0" encoding="utf-8"? > <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.CustomActivity"> · · · · · · <. Com. Sasucen softinput. Widget. EmojiPanelView android: id ="@+id/layout_face_panel"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            app:layout_constraintBottom_toBottomOf="parent">

    </com.sasucen.softinput.widget.EmojiPanelView>

</android.support.constraint.ConstraintLayout>

Copy the code


The above is my own summary of the soft keyboard, I hope I can come back to have a look when I am not clear next time, but also hope to help people in need. If there are fallacies, please also point out!

The source code