Android keyboard related FAQ:

  1. Limit the number of words in the input box, do not let input more than the number of words, and prompt
  2. Click on the external area and the keyboard automatically retracts
  3. How do I get the keyboard height
  4. Keyboard and panel toggle conflict

Here’s a look at each of these issues.

1. Limit the number of words in the input box. If the number of words exceeds the number of words, no input is allowed and a prompt is displayed

etReply.setFilters(new InputFilter[]{new InputFilter() {
    @Override
    public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
        if (source.length() + dest.length() > COMMENT_MAX_NUM) {
            Crouton.makeText(AppUtils.getString(R.string.infodetail_comment_limit), Style.ALERT).show();
        }
        return null; }},new InputFilter.LengthFilter(COMMENT_MAX_NUM)});
Copy the code

2. Click the external area keyboard to automatically fold up

You can override the dispatchTouchEvent method directly if the current page is an Activity. During the ACTION_DOWN event, determine whether the clicked coordinate is above the input box coordinate, and if so, call the hidden keyboard method.

If the current page is a Fragment, then add a dispatchTouchEvent method to the Fragment with the same internal logic as above, Pass the dispatchTouchEvent event to the Fragment’s dispatchTouchEvent in the Activity code that the Fragment depends on. If the keyboard needs to be hidden, The Fragment’s dispatchTouchEvent method should return true to consume all touch events.

3. How do I get the keyboard height

The first thing to know is that the keyboard height is not fixed. Users use different input methods, the height may be different; Even some input methods, you can directly adjust the height of the input method panel.

3.1 Is there any systematic API for us to get the keyboard height?

No.

3.2 Is there any way to obtain the keyboard height indirectly?

The system provides us with an OnGlobalLayoutListener for page layout changes. This listener can notify us of the layout changes, and we can get our own height at this time, and indirectly calculate the height of the keyboard through screen width, status bar height, etc.

  • So there’s a question, does the OnGlobalLayoutListener have to pop up or disappear when it receives a change in action?

    Not necessarily.

  • Why can OnGlobalLayoutListener listen to the status of the keyboard?

    We know that the OnGlobalLayoutListener is emitted when each view changes width or height, but generally the size of the DecorView does not change when we listen to it in the Window object of the current Activity. The main reason for the change is the keyboard’s folding and unfolding, which, along with a simple judgment (the change exceeds a certain threshold), allows you to get the height of the keyboard and whether it’s up or down.

  • Do you use OnGlobalLayoutListener to listen for keyboard failures?

    On Android7.0, we can use the multitasking key to enable the split screen/multi-window mode. When we open the split screen and adjust the split screen boundary, the DecorView’s OnGlobalLayoutListener is triggered, but the keyboard does not trigger any action. In addition, when we click on an input box, the keyboard will hover in split screen mode without squeezing the Activity controls, so when the keyboard pops up or collapses, the OnGlobalLayoutListener will not receive any events. This causes the OnGlobalLayoutListener to completely fail. There are some other scenarios where listening for keyboard events is disabled, but I can’t think of them at the moment, so I can add them in the comments.

3.3 Obtaining the Keyboard Height

In general, we listen for changes in the layout of the DecorView in the Activity’s PhoneWindow. Generally, changes of more than 60DP are considered keyboard pop-ups or collapses. And in a non-full-screen theme, keyboard height = screen height – status bar height – Main view height – Title bar height, so we can indirectly calculate the keyboard height by using the following code.

mDecorView = this.getActivity().getWindow().getDecorView();
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout(a) {
        Rect rect = new Rect();
        mDecorView.getWindowVisibleDisplayFrame(rect);
        // You cannot use decorView.getheight () to get the height of the decorView. The height will not change
        int displayHeight = rect.bottom;
        if (Math.abs(displayHeight - mOldDecorViewHeight) > dp60) {
            mOldDecorViewHeight =displayHeight;
            int rootHeight = mRoot.getHeight();
            int statusBarHeight = getStatusBarHeight();
            int screenHeight = getScreenHeight();
            int titleBarHeight = getTitleBarHeight();
            // In non-full screen mode, keyboard height = screen height - status bar height - Main view height - Title bar height
            int keyboardHeight = screenHeight - statusBarHeight - rootHeight - titleBarHeight;
            Log.i("lxc"."keyboardHeight ---> " + keyboardHeight + "Keyboard:" + (keyboardHeight > 0 ? "Pop" : "Fold")); }}}; mDecorView.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);Copy the code

When I click on the input box to pop up and put away the keyboard, the following log appears:

“‘ the console 05-29 15:22:50. 368, 8982-8982 / com. Orzangleli. Myapplication I/LXC: keyboardHeight – > 0 keyboard: Fold 05-29 15:22:51. 736, 8982-8982 / com. Orzangleli. Myapplication I/LXC: keyboardHeight — – > 873 keyboard: Pop-up 05-29 15:22:52. 739, 8982-8982 / com. Orzangleli. Myapplication I/LXC: keyboardHeight – > 0 keyboard: Fold 05-29 15:22:53. 892, 8982-8982 / com. Orzangleli. Myapplication I/LXC: keyboardHeight — – > 873 keyboard: pop up ‘ ‘ ‘

4. The keyboard and panel switch conflict

4.1 Problem Description

On IM chat pages, the bottom is usually made to look like wechat (click to switch emoji panel and keyboard). Click the emoji button, the emoji panel will pop up, and the emoji button will become the keyboard mode; Click the keyboard mode again, or click the input box, and the input box will pop up and the emoji panel will be closed. The following sections are all called emoji panels.

4.2 General Ideas

Usually such a page layout is a RecyclerView+ input area. The input area is under RecyclerView, so the entire layout can use a vertical LinearLayout. For keyboard mode, we choose adjustResize.

The general logic is as follows:

  • The input area contains the input box and the following emoji panel, the default emoji panelvisibilityforGONE.
  • When clicking the emoji button, the visibility of the panel isVISIBLE; Put away the input keyboard; The button image changes to keyboard mode.
  • Click the keyboard mode button again and the visibility of the panel isGONE; Expand the input keyboard; Push the button into emoji mode.

Let’s write down the key code along these lines:

private void initView(View root) {
    mInputEt = root.findViewById(R.id.et_input);
    mFaceBtn = root.findViewById(R.id.btn_face);
    mPanel = root.findViewById(R.id.panel);

    mFaceBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mPanel.getVisibility() == View.VISIBLE) {
                mPanel.setVisibility(View.GONE);
                mFaceBtn.setImageResource(R.drawable.emoji_download_icon);
                openKeyBoard(mInputEt);
            } else{ mPanel.setVisibility(View.VISIBLE); mFaceBtn.setImageResource(R.drawable.zz_chat_reply_keyboard); closeKeyBoard(mInputEt); }}}); }Copy the code

Run to see the result:

There was a strange frame:

Conclusion:

When the keyboard is already on the screen, we need to hide the keyboard before clicking the emoticon button to pop up the panel, but by hiding the keyboard we are just calling a remote service (context.input_method_service), and we have no control over when it is executed (generally speaking, it involves cross-process communication, So the execution sequence must be after the panel display), so whether we call the hidden keyboard API and then show the emoji panel, or whether we call the hidden keyboard API and then show the emoji panel, this frame will appear, giving the impression of a flicker.

4.3 Solution

Therefore, the time to hide and display the emoji panel is not immediately executed when we click the emoji button, but we need to wait until the input method panel is fully displayed or hidden.

There may be some knowledge involved in listening for keyboard bounce/hide operations and getting keyboard height. See the previous section for a summary of how to get keyboard height. The keyboard height we get is directly stored in SharedPreferences after each update. Some applications need to reset the height of the panel to be the same as the keyboard height. For example, wechat needs to record the keyboard height. But not every application needs the panel to be the same height as the keyboard, and if your application doesn’t you don’t have to look at how to get the keyboard height.

If we listen for the keyboard to pop up or collapse in OnGlobalLayoutListener, and set the panel to hide or show according to the corresponding state, we will have some blinking problems (see the code in the Demo). Because the flashing time is very short, so when recording GIF cannot see, interested can run the Demo to solve the switching keyboard conflict solution.

In the case of keyboard tapping, our process goes like this:

Trigger keyboard up –> OnGlobalLayoutListener receives layout change –> Keyboard is fully up now –> X –> Hide emoji panel

The X in this process refers to the fact that the emoji keyboard is not immediately hidden when the bug occurs, but later hidden when the keyboard is fully up, resulting in a faint flicker of semi-resolved conflict.

  • Why this faint flicker?

Let’s take a look at the ViewGroup measurement process. When the ViewGroup is measured, it will first traverse the size of all subviews, and then calculate the appropriate size based on the ViewGroup measurement mode. In this case, when the expression panel has been expanded, if you switch to the keyboard, the home page keyboard will squeeze the whole layout, that is, the layout of ViewGroup as we said, but when the onMeasure of ViewGroup is implemented, the expression panel inside is still visible. Then in the OnGlobalLayoutListener callback we set the viewability of the emoji panel to GONE, but it is already in a different frame than when the keyboard was first expanded, so we see a faint flicker effect.

Based on the analysis above, we need to measure the ViewGroup size in the frame when the keyboard is down, and directly measure the panel control size. We put the emoji area into a custom layout control, KBPanelConflictLayout, and set the root layout of the entire page to a custom control, KBRootConflictLayout (see Demo for code that solves switching keyboard conflicts).

In the onMeasure method of KBRootConflictLayout, judge whether the keyboard is up according to whether the layout height change exceeds a certain threshold.

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    preNotifyChild(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
    
private void preNotifyChild(int width, int height) {
    if (mOldHeight < 0) {
        mOldHeight = height;
        return ;
    }
    int deltaY = height - mOldHeight;
    mOldHeight = height;
    int minKeyboardHeight = 180;
    if (Math.abs(deltaY) >= minKeyboardHeight) {
        if (deltaY < 0) {
            // The keyboard is playing
            if(mKBPanelConflictLayout ! =null) {
                // Hide the panelmKBPanelConflictLayout.setHide(); }}else {
            // Put away the keyboard
            if(mKBPanelConflictLayout ! =null) {
                // Display panelmKBPanelConflictLayout.setShow(); }}}}Copy the code

In KBPanelConflictLayout’s onMeasure method, we determine whether we need to change the keyboard height to 0 based on whether the state is hidden.

 @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    if (mHide) {
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
    }
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

public void setHide(a) {
    this.mHide = true;
    setVisibility(View.GONE);
}

public void setShow(a) {
    this.mHide = false;
    setVisibility(View.VISIBLE);
}
Copy the code

In this test, see the effect of no flicker conflict.

If you take this code and optimize it, add support for RelativeLayout and FrameLayout, support for adjusting the height of a panel to match the keyboard, support for switching between panels, provide some utility classes that users can call directly, That’s 2000+ Star github.com/Jacksgong/J… The project.

Attached is the address of this article Demo, welcome refreshments:

Github.com/hust2010107…