Solution to the Problem of Covering Input Box When Android Input Method Pops Up

  • 2021-07-03 00:51:49
  • OfStack

When an activity contains an input box, when we click on the input box, the input method interface will pop up. The change effect of the whole interface is related to the android: windowSoftInputMode attributes set correspondingly in manifest. Generally, the values that can be set are as follows.


<activity android:windowSoftInputMode=[
"stateUnspecified",
"stateUnchanged " , 
"stateHidden",
"stateAlwaysHidden " , 
"stateVisible",
"stateAlwaysVisible " , 
"adjustUnspecified",
"adjustResize " , 
"adjustPan"]  ...  >

Specific how to set can view official documents. Today, we mainly solve the problem that when the input method pops up, it will overwrite the input box.

What will be covered?

In the application of android, if one activity sets the full screen attribute Theme. Light. NotittleBar. Fullscreen or sets the android: windowTranslucentStatus attribute in the subject corresponding to activity, the setting mode is as follows: < item name="android:windowTranslucentStatus" > true < /item > If the corresponding page contains an input box, it will cause the soft keyboard to pop up when clicking the input box, and the keyboard will overwrite the input box, resulting in the input box being invisible.

Why?

This is actually because the adjustResize attribute has expired in full screen. The problem is that the system has 1 bug, refer to the link. adjustResize does not work, is there any other way to solve it? At this time, we can set the adjust attribute to adjustPan attribute, which will not fail. However, because adjustPan will translate the whole page to leave room for input method, there will be a jitter effect, and the experience is very poor. Is there a better way to experience the effect?

Solution:

If FrameLayout is used with the layout, you can override a custom FrameLayout and set the android: fitsSystemWindows attribute of FrameLayout to true. The xml settings are as follows


<com.sample.ui.widget.InsetFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:layout_width="match_parent"
          android:layout_height="match_parent"
          android:fitsSystemWindows="true " >

We customize the FrameLayout as InsetFrameLayout, and the code of InsetFrameLayout is as follows:


public final class InsetFrameLayout extends FrameLayout {
  private int[] mInsets = new int[4];

  public InsetFrameLayout(Context context) {
    super(context);
  }

  public InsetFrameLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
  }

  public InsetFrameLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
  }

  public final int[] getInsets() {
    return mInsets;
  }

  @Override
  protected final boolean fitSystemWindows(Rect insets) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
      // Intentionally do not modify the bottom inset. For some reason,
      // if the bottom inset is modified, window resizing stops working.

      mInsets[0] = insets.left;
      mInsets[1] = insets.top;
      mInsets[2] = insets.right;

      insets.left = 0;
      insets.top = 0;
      insets.right = 0;
    }

    return super.fitSystemWindows(insets);
  }
  @Override
  public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
      mInsets[0] = insets.getSystemWindowInsetLeft();
      mInsets[1] = insets.getSystemWindowInsetTop();
      mInsets[2] = insets.getSystemWindowInsetRight();
      return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0,
          insets.getSystemWindowInsetBottom()));
    } else {
      return insets;
    }
  }

}

Official solution:

Officials actually found the problem, so FrameLayout was rewritten under android. support. design. internal to solve the problem, but this class was marked hide.


/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.support.design.internal;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.design.R;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;

/**
 * @hide
 */
public class ScrimInsetsFrameLayout extends FrameLayout {

  private Drawable mInsetForeground;

  private Rect mInsets;

  private Rect mTempRect = new Rect();

  public ScrimInsetsFrameLayout(Context context) {
    this(context, null);
  }

  public ScrimInsetsFrameLayout(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public ScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    final TypedArray a = context.obtainStyledAttributes(attrs,
        R.styleable.ScrimInsetsFrameLayout, defStyleAttr,
        R.style.Widget_Design_ScrimInsetsFrameLayout);
    mInsetForeground = a.getDrawable(R.styleable.ScrimInsetsFrameLayout_insetForeground);
    a.recycle();
    setWillNotDraw(true); // No need to draw until the insets are adjusted

    ViewCompat.setOnApplyWindowInsetsListener(this,
        new android.support.v4.view.OnApplyWindowInsetsListener() {
          @Override
          public WindowInsetsCompat onApplyWindowInsets(View v,
              WindowInsetsCompat insets) {
            if (null == mInsets) {
              mInsets = new Rect();
            }
            mInsets.set(insets.getSystemWindowInsetLeft(),
                insets.getSystemWindowInsetTop(),
                insets.getSystemWindowInsetRight(),
                insets.getSystemWindowInsetBottom());
            setWillNotDraw(mInsets.isEmpty() || mInsetForeground == null);
            ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this);
            return insets.consumeSystemWindowInsets();
          }
        });
  }

  @Override
  public void draw(@NonNull Canvas canvas) {
    super.draw(canvas);

    int width = getWidth();
    int height = getHeight();
    if (mInsets != null && mInsetForeground != null) {
      int sc = canvas.save();
      canvas.translate(getScrollX(), getScrollY());

      // Top
      mTempRect.set(0, 0, width, mInsets.top);
      mInsetForeground.setBounds(mTempRect);
      mInsetForeground.draw(canvas);

      // Bottom
      mTempRect.set(0, height - mInsets.bottom, width, height);
      mInsetForeground.setBounds(mTempRect);
      mInsetForeground.draw(canvas);

      // Left
      mTempRect.set(0, mInsets.top, mInsets.left, height - mInsets.bottom);
      mInsetForeground.setBounds(mTempRect);
      mInsetForeground.draw(canvas);

      // Right
      mTempRect.set(width - mInsets.right, mInsets.top, width, height - mInsets.bottom);
      mInsetForeground.setBounds(mTempRect);
      mInsetForeground.draw(canvas);

      canvas.restoreToCount(sc);
    }
  }

  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (mInsetForeground != null) {
      mInsetForeground.setCallback(this);
    }
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (mInsetForeground != null) {
      mInsetForeground.setCallback(null);
    }
  }

}

Adopting any one of the above methods can solve the problem of overwriting the input box after the input method pops up.

Other questions?

In the process of using it, we found user feedback, saying that as long as we enter the page with this layout, it will crash. We checked the crash log and found that some mobile phones all use the same Android system, and the version is 19, android4.4. x, a rewritten system, and the code loading method of this system has been rewritten.

Why did it collapse?

Our code uses WindowInsets, which is provided by api 20, so there is no such code in the system of 19, but the system parsed this class when inflate of xml, resulting in classNotFound.

New solution!

The new solution still adopts the above-mentioned method, but different layouts will be written for different versions, providing different layouts for api above 20 and below 20 respectively, which is realized by the qualifier of the system. After that, the original above 20 adopts the above-mentioned method, and the replication of onApplyWindowInsets is removed below 20, so that different versions load different codes on OK.


 @Override
  public final WindowInsets onApplyWindowInsets(WindowInsets insets) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
      mInsets[0] = insets.getSystemWindowInsetLeft();
      mInsets[1] = insets.getSystemWindowInsetTop();
      mInsets[2] = insets.getSystemWindowInsetRight();
      return super.onApplyWindowInsets(insets.replaceSystemWindowInsets(0, 0, 0,
          insets.getSystemWindowInsetBottom()));
    } else {
      return insets;
    }
  }

To sum up, the whole solution has been completed. If there are updated solutions, please share them.


Related articles: