Android全屏状态下弹出输入法adjustResize无效的修复方案及踩坑指南

Android全屏状态下弹出输入法adjustResize无效的修复方案及踩坑指南

输入框被输入法遮挡的问题相信很多人都遇见过,这次记录的是Activity在全屏FullScreen状态下windowSoftInputMode的adjustResize不生效问题。

下面是WindowManager类关于SOFT_INPUT_ADJUST_RESIZE的官方注释,在FullScreen状态下adjustResize flag会被忽略。

/** Adjustment option for {@link #softInputMode}: set to allow the
 * window to be resized when an input
 * method is shown, so that its contents are not covered by the input
 * method.  This can not be combined with
 * {@link #SOFT_INPUT_ADJUST_PAN}; if
 * neither of these are set, then the system will try to pick one or
 * the other depending on the contents of the window. If the window's
 * layout parameter flags include {@link #FLAG_FULLSCREEN}, this
 * value for {@link #softInputMode} will be ignored; the window will
 * not resize, but will stay fullscreen.
 */
public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;

直接Google搜索FullScreen AdjustResize失效的相关资料,可以看到很多相关资料都指向Google的bug issue 5497,并且有现成的解决方案,大概有3种:

  1. 在输入法弹出的时候改变rootView或decorView的高度,使view的高度 = 屏幕高度 - 输入法高度
  2. 在输入法弹出的时候改变rootView的底部padding边距(当输入框置于view的底部时),padding值跟输入法高度相等
  3. 在输入法弹出的时候使rootView垂直向上平移,平移高度跟输入法高度相等

以上3种方案选择哪一种需要看具体需求,如果需求需要View完整显示出来而且可以压缩显示时,选择1、2方案,否则选择方案3。

鸡汁的我,直接选择方案1,并且使用网上各路大神发出来的现成工具类AndroidBug5497Workaround,运行一遍,完美,直接把测试报过来的BUG关闭掉,文章到此结束。



太天真了,小米2S出问题,小米MIX3出问题,三星S8出问题,而且表现各不相同。。
Android全屏状态下弹出输入法adjustResize无效的修复方案及踩坑指南_第1张图片


三种机型出现的问题的表现
  1. 小米2S:弹出输入法时,如下图所示,内容区域高度缩小正常,但内容延伸到了屏幕外围,输入法与输入框之间出现一大段空白间距,间距基本上等于输入法的高度。
    Android全屏状态下弹出输入法adjustResize无效的修复方案及踩坑指南_第2张图片

  2. 小米MIX3:弹出输入法时,如下图所示,内容区域高度缩小失败,部分内容延伸到屏幕外围,输入框部分区域被输入法遮盖,输入框光标刚好展示完,基本上跟AdjustPan的效果一致。
    Android全屏状态下弹出输入法adjustResize无效的修复方案及踩坑指南_第3张图片

  3. 三星S8:输入法缩小收起时,输入框监听界面布局变化回调失效,输入框没有相应进行隐藏操作,并出现部分输入框显示在页面底端,部分延伸到屏幕外。
    Android全屏状态下弹出输入法adjustResize无效的修复方案及踩坑指南_第4张图片


兼容性事故现场分析:

此处通过AS的布局分析神器Layout Inspector大量分析,得出以下结论,分析过程本篇略过。

  1. 小米2S的表现在部分5.0或以下机器中会出现,通过分析得到在输入法弹出后decorView有一个纵向向上的偏移,偏移距离大致等于输入法高度,这个偏移其实就是AdjustPan的逻辑。再查看rootView的高度,发现高度是我们设进去的 (屏幕总高度 - 输入法高度)。所以中间空白区域的出现原因就是系统在AdjustPan之后我们又再次设定了rootView高度,而rootView是默认布局在顶部的 (top to top of parent),导致rootView大部分内容显示在屏幕外,下方则是空白的decorView。

  2. 小米MIX3的表现,基本上就是标准的AdjustPan了,我们自己设定的高度失效,在页面弹出输入法的时候可以看到rootView有一瞬间的闪烁,看上去效果就像内容区域往上偏移后再恢复回来。经过分析,它的执行顺序跟小米2S相反,小米2S是先执行AdjustPan,再设置rootView的高度,此机型则是先设置高度,再执行AdjustPan,AdjustPan之后高度会被强行恢复回来,所以会看到弹出输入法时又一瞬间的闪烁。

  3. 三星S8的问题通过断点调试,发现原来输入框监听界面变化时的回调并没有被调用,但AndroidBug5497Workaroun类里面的监听则是正常,为什么旧的监听会被新加入的监听截断,这里没有详细分析,只要在AndroidBug5497Workaroun里面增加一个输入法隐藏的接口到外部页面即可解决这个问题。

问题1和问题2属于同一类型的问题,解决思路是使系统不执行AdjustPan逻辑。接下来就是各种试验,给Activity加上AdjustNothing,发现行不通;代码设置AdjustNothing,行不通;甚至还想过要不要根据系统版本来区别执行逻辑,如果是5.0以下则给输入框增加一点底部边距,后来也否了这个方案。

在多次试验后,发现如果输入框不显示,多个机型弹出输入法后,rootView的高度都是能正常缩减的,刚好布满输入法以上的显示区域。然后把输入框的显示逻辑慢慢拆减处理,最后问题定位到输入框的焦点上。如果在弹出输入法之前,焦点已经被输入框获得,并且输入法弹出后会遮盖输入框的焦点,部分机型会执行AdjustPan逻辑,至于执行后是什么表现,这个就要看机子的脾气了~

弹出输入法 -> AndroidBug5497Workaroun进行rootView高度缩减 -> 显示输入框 -> 给予输入框焦点

最后我把代码执行顺序进行调整,把输入框的焦点获取放到最后,问题解决。下面是正在使用的全屏输入法AdjustResize问题工具类,基于AndroidBug5497Workaroun类修改。需要注意的是,输入框的焦点必须放到InputShowListener回调内执行,即可修复部分机型的兼容性问题。

/**
 * 用于修复全屏状态下adjustResize不生效的问题,当弹出输入法时重新设定内容view的高度,使输入框正常显示
 */
public class FullscreenInputWorkaround {

    // For more information, see https://code.google.com/p/android/issues/detail?id=5497
    // To use this class, simply invoke assistActivity() on an Activity that already has its content view set.
    private static final String TAG = "AndroidBug5497Workaroun";

    public static FullscreenInputWorkaround assistActivity(Activity activity, View contentView, InputShowListener inputShowListener) {
        return new FullscreenInputWorkaround(activity, contentView, inputShowListener);
    }

    private Activity activity;
    private View mChildOfContent;
    private int usableHeightPrevious;
    private ViewGroup.LayoutParams layoutParams;

    private FullscreenInputWorkaround(Activity activity, View contentView, InputShowListener inputShowListener) {
        this.activity = activity;
        this.inputShowListener = inputShowListener;
        mChildOfContent = contentView;
        mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                possiblyResizeChildOfContent();
            }
        });
        layoutParams = mChildOfContent.getLayoutParams();
    }

    private void possiblyResizeChildOfContent() {
        int usableHeightNow = computeUsableHeight();
        if (usableHeightNow != usableHeightPrevious) {
            int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();

            int heightDifference = usableHeightSansKeyboard - usableHeightNow;
            if (heightDifference > (usableHeightSansKeyboard / 4)) {
                // keyboard probably just became visible
                layoutParams.height = usableHeightSansKeyboard - heightDifference;
                if (inputShowListener != null) {
                    inputShowListener.inputShow(true);
                }
            } else {
                // keyboard probably just became hidden
                layoutParams.height = usableHeightSansKeyboard;
                if (inputShowListener != null) {
                    inputShowListener.inputShow(false);
                }
            }
            mChildOfContent.requestLayout();
            usableHeightPrevious = usableHeightNow;
        }
    }

    private int computeUsableHeight() {
        Rect frame = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
        int statusBarHeight = frame.top;

        Rect r = new Rect();
        mChildOfContent.getWindowVisibleDisplayFrame(r);

        //这个判断是为了解决19之后的版本在弹出软键盘时,键盘和推上去的布局(adjustResize)之间有黑色区域的问题
        if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
            return (r.bottom - r.top) + statusBarHeight;
        }

        return (r.bottom - r.top);
    }

    private InputShowListener inputShowListener;

    public interface InputShowListener {
        void inputShow(boolean show);
    }
}

你可能感兴趣的:(android)