轻框 webView 支持软键盘

 1.常用软键盘模式API

  API 场景:#Activity \ #AlertDialog \ #PopupWindow \  ...

  getWindow().setSoftInputMode(int flag {@link WindowManager.LayoutParams.#softInputMode));

1.SOFT_INPUT_ADJUST_PAN

系统会选择一种合理的方式平移window,使得获取焦点的输入框可见,不会改变布局大小。

 
 

2.SOFT_INPUT_ADJUST_RESIZE

软键盘弹出时window大小可被调整,使得可见内容不被覆盖,该属性与SOFT_INPUT_ADJUST_PAN互斥,不能同时使用。

注:API中指出,window添加属性中存在 {@link #WindowManager.LayoutParams.# FLAG_FULLSCREEN}情况,

系统会忽略SOFT_INPUT_ADJUST_RESIZE属性,解决方案见下文。

 

3.SOFT_INPUT_ADJUST_NOTHING

软键盘弹出时window不被平移也不会改变大小。

4.SOFT_INPUT_ADJUST_UNSPECIFIED

系统会根据window内容选择一种显示方式,一般不推荐,不同的Android版本可能存在差异,会导致页面兼容性问题。

2.问题场景 & 解决方案

1.Android文本可编辑控件<EditText>场景,例如 评论、登录、个人中心 ...

推荐使用# SOFT_INPUT_ADJUST_PAN模式,系统根据当前焦点文本框的位置平移window,交互体验较好。

2.WebView应用场景

  • a. WebView场景中由Android文本编辑控件<EditText>触发软键盘,此时与场景1没有区别,使用#SOFT_INPUT_ADJUST_PAN模式;
  • b. WebView场景中由H5元素触发软键盘,此时使用#SOFT_INPUT_ADJUST_RESIZE模式,使得window改变大小,保证WebView内容不被遮挡。

3.全屏窗口使用场景<#FLAG_FULLSCREEN>

FULLSCREEN效果是全屏显示(SystemUI StatusBar 会被置于底层,activity的window置于上层),此时如果涉及软键盘弹出遮挡问题则不能使用SOFT_INPUT_ADJUST_RESIZE方式开解决。

 

3. 轻框 webview 解决方案

3.1 背景:

目前线上 webview 里的 H5 点击输入框没有反应,因为webview被放在全屏模式下了。但是你也不可能去更改模式,这样影响会比较大。目前就是需要在保证业务逻辑不受影响的情况下去,如何让轻框弹出软键盘。

3.2 解决方案:

百度一搜会发现很多webview都出现了类似问题,但是最早的问题可以见 WebView adjustResize windowSoftInputMode breaks when activity is fullscreen 一文,这是开发者最早在 android 源码上提出来的bug,不过bug级别为 P3,一直也没有给解决。

 

在 stackoverflow 上也有用户提出了问题,Android How to adjust layout in Full Screen Mode when softkeyboard is visible,文章后面有人给出了答案:

public class AndroidBug5497Workaround {

    // For more information, see https://issuetracker.google.com/issues/36911528
    // To use this class, simply invoke assistActivity() on an Activity that already has its content view set.

    public static void assistActivity (Activity activity) {
        new AndroidBug5497Workaround(activity);
    }

    private View mChildOfContent;
    private int usableHeightPrevious;
    private FrameLayout.LayoutParams frameLayoutParams;

    private AndroidBug5497Workaround(Activity activity) {
        FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
        mChildOfContent = content.getChildAt(0);
        mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                possiblyResizeChildOfContent();
            }
        });
        frameLayoutParams = (FrameLayout.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
                frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
            } else {
                // keyboard probably just became hidden
                frameLayoutParams.height = usableHeightSansKeyboard;
            }
            mChildOfContent.requestLayout();
            usableHeightPrevious = usableHeightNow;
        }
    }

    private int computeUsableHeight() {
        Rect r = new Rect();
        mChildOfContent.getWindowVisibleDisplayFrame(r);
        return (r.bottom - r.top);
    }
}
 

使用方式是在对应的 activity 页面的 onCreate 中进行调用即可:

AndroidBug5497Workaround.assistActivity(this);

3.3 方案应用

看到解决方案后,内心狂喜,就这么简单啊。开心的将其copy到项目中,编译,安装,启动。打开用轻框承载的页面,激动的点下,键盘弹起来了。哇塞,问题解决了。多次几次后发现还是存在一些问题:

  1. 有时候页面无法滚动起来;
  1. 键盘弹起后,落下的时候,可以见到底部有一块黑色阴影,这个是很影响用户体验的;
  1. 目前的修改方案将返回条也顶上来了;
 

3.4 完善方案

问题1

针对3.3中的问题1,猜测是失去焦点了,导致没有把焦点的 view 平移出来。可是为啥有时候有问题,有时候没问题呢,不觉得这个是很诡异的事情吗?

于是又开始疯狂百度和Google,结果发现竟然没有人跟我有一样的遭遇。这,为何只有我需要承受这种伤害。最终在经历了两天多的各种尝试和自我怀疑后,突然想到我现在使用的是T7内核,那我如果不使用呢?很快,我在 debug 模式下禁用了 T7 内核,使用原生内核,在尝试后,竟然没有问题了,喜大普奔啊。于是,我将问题反馈给内核同学,后来内核同学发现代码在处理焦点逻辑的时候存在问题,但是具体解决方案短期还是无法给出。

问题2

针对3.3中的问题2,之所以会出现黑色背景,是因为我们将 contentview 的大小给改变了,导致漏出来的黑色背景。解决这个问题有两种方式

  1. 一种是设置背景颜色,但是这样的话,代码使用上有点打折扣。
  1. 另一种方案是只改变 webview 外层容器 container 的高度,这样即使container高度变了, 还是有背景的。

问题3

其实这个问题跟问题2的第二种解法类似,就是改变 webview 外层容器 container 的高度,不要去更改包含返回条的view即可;

问题4

解决上述三个问题后,提测。

QA测试中又发现一个新的问题,在部分屏幕高度有限的手机中,底部的举报按钮被返回条遮挡了,但是无法滑动。

 

如上图所示,可以看到底部的提交按钮被返回条遮挡了,并且页面不能滑动。

那么页面为啥不能滑动了呢?

原因:当你用网上给的解决方案修改内层view的时候,最后是将当前window的可见高度赋给了内层view,这个高度和其原始高度并不一定是相等的。当你多给了一些高度,会让原本不可见的内容变成了可见,于是view就变成不可滑动的呢。

解决这个问题的方案也很简单,那就是恢复其原来的高度。

  1. 第一种修复方案是,当键盘收起之后,将 view 的高度变成 ViewGroup.LayoutParams.MATCH_PARENT,这时候会触发重新测量等逻辑。
frameLayoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;

但是这种方案不推荐就是这么改可能会导致绘制次数变多,影响页面性能;

2、第二种修复方案是,在键盘弹起的时候,记录此时需要改变的 container 的高度,在键盘收起后,恢复原始高度即可。

到这里,应该是比较完美了。

但是这里还有个问题,就是他会监听每次绘制,然后触发再次绘制,可能会导致线上绘制次数变多。好的办法就是只有在键盘弹起和收起之间去做这个操作。

    /** 屏幕高度比例,0.85来源{@link AbsBdFrameView} */
    private static final float RATE = 0.85f;
    /** 上一次的可见高度 */
    private int mLastVisibleHeight;
    /** 保存视图键盘未弹出时高度值 */
    private int mViewHeight;
    /** 布局参数 */
    private ViewGroup.LayoutParams mParams;
    /** OnGlobalLayoutListener */
    private ViewTreeObserver.OnGlobalLayoutListener mOnGlobalLayoutListener;
    /** rootView */
    private ViewGroup mRootView;
    /** Resume 生命周期状态码 */
    private boolean mIsResuming;

    public KeyBoardWorkaround(@NonNull ViewGroup rootView) {
        mRootView = rootView;
        addOnGlobalLayoutListener();
    }

    /**
     * 添加layout回调
     */
    private void addOnGlobalLayoutListener() {
        mOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                resizeRootView();
            }
        };
        mRootView.getViewTreeObserver().addOnGlobalLayoutListener(mOnGlobalLayoutListener);
        mParams = mRootView.getLayoutParams();
    }

    /**
     * 调整rootView的大小
     */
    private void resizeRootView() {
        // 屏幕的高度 * 0.85得到一个处于输入法弹起后页面可见的高度(0.85来源{@link AbsBdFrameView})
        int hasKeyboardHeight = (int) (DeviceUtils.ScreenInfo.getRealScreenHeight(mRootView.getContext()) * RATE);
        // webView + 输入法的状态下,保证恢复高度的layout不会被过滤掉而出现白屏问题{@link AbsBdFrameView}
        if (mLastVisibleHeight >= hasKeyboardHeight && !mIsResuming) {
            return;
        }
        Rect r = new Rect();
        // 这里获取的是window的可见高度,而不是mRootView的可见高度
        mRootView.getWindowVisibleDisplayFrame(r);
        int displayHeight = r.bottom - r.top;
        int rootViewHeight = mRootView.getHeight();
        // view可见高度&尺寸均大于键盘弹出时的高度,说明此时无键盘弹出和收起情况,直接返回
        if (Math.min(displayHeight, rootViewHeight) > hasKeyboardHeight) {
            return;
        }
        // 键盘状态变更
        if (displayHeight != rootViewHeight && displayHeight > 0 && rootViewHeight > 0) {
            // 该条件为true时表示键盘弹出
            if (displayHeight < hasKeyboardHeight) {
                mParams.height = displayHeight;
                // 记录view原始高度
                mViewHeight = rootViewHeight;
            } else {
                mParams.height = mViewHeight;
            }
            mLastVisibleHeight = mParams.height;
            mRootView.requestLayout();
        }
    }

到这里,关于webview软键盘的问题就解决了。方案经过多次优化后,已经变得比较完美了。

posted @ 2023-05-10 00:27  huansky  阅读(390)  评论(0编辑  收藏  举报