Webview中键盘适配的一些坑

Wanjiongming 于 2020-04-29 发布

在 Android 原生开发中,可以通过设置 windowSoftInputMode,来处理键盘弹起时 Layout 的适配方式。有两种方案,默认 adjustPan 高度变化压缩布局,还有一种是 adjustResize 高度不变布局整体向上平移

看起来似乎很完美,但是 windowSoftInputMode 对于在 webview 中弹起的键盘似乎是无效的

经过一番百度之后发现这是 Android 的一个 Bug,只要 APP 对状态栏做了设置如状态色着色、浸式状态栏等,都会导致 windowSoftInputMode 对 webview 无效

由于在主题中设置了 windowTranslucentStatus,那就确实是这个导致的。但是经过测试,就算不使用全屏,不设置状态栏沉浸,甚至把主题调到了Theme.Holo.Light.DarkActionBar还是有上述问题

解决方案也很简单,使用一个神奇的 AndroidBug5497Workaround 类就好了。这个类实际上通过动态改变高度来实现系统的 adjustResize。所以 adjustPan 仍然是无效的

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    AndroidBug5497Workaround.assistActivity(this);
}

同时以防万一还是设置下 windowSoftInputMode

<activity
    android:name=".MainActivity"
    android:windowSoftInputMode="adjustResize">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

跳出一个坑其实还有一个坑。由于在 html 中使用了 vh 作为高度单位,规定上 100vh 应该就等于设备的高度,在 IOS 中这个没问题,但是在 Android 中,100vh 等于的是 webview 的高度,这 TM 就调皮了

由于上面使用了 adjustResize 的方案,所以当键盘弹起时,webview 高度变化,100vh 对应的值也就发生变化了,导致页面被严重压扁

这个属于系统对 vh 的定义不同,原生代码没法适配了,只好设置 JS 去设置 viewport 去适配这个问题。原理很简单,在 viewport 强制设置 height 为固定值就可以了

const maxHeight = window.innerHeight
function fixedViewportHeight(fixed) {
  let metaEl = document.querySelector('meta[name=viewport]')
  let content = []
  content.push('width=device-width')
  fixed ? content.push('height=' + maxHeight) : ''
  content.push('initial-scale=1.0')
  content.push('user-scalable=no')
  metaEl.setAttribute('content', content.join(','))
}

注意要开启 webview 对 viewport 的支持

WebSettings webSettings = this.getSettings();
webSettings.setJavaScriptEnabled(true);
webSettings.setCacheMode(WebSettings.LOAD_DEFAULT);
webSettings.setJavaScriptCanOpenWindowsAutomatically(true);
webSettings.setSupportZoom(true); // 支持缩放
webSettings.setBuiltInZoomControls(true); // 设置内置的缩放控制 手势 + 缩放控件
webSettings.setDisplayZoomControls(false); // 不显示缩放控件
webSettings.setSaveFormData(false);
webSettings.setUseWideViewPort(true); // 支持html设置viewport
webSettings.setLoadWithOverviewMode(true); // body宽度超出自动缩放
webSettings.setDatabaseEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setGeolocationEnabled(true);
webSettings.setAppCacheEnabled(true);
webSettings.setAllowFileAccess(true); // 支持以 file:/// 打开本地文件,file:///android_asset 是默认被允许的
webSettings.setAllowFileAccessFromFileURLs(false); // 本地文件能否通过ajax访问别的本地文件
webSettings.setAllowUniversalAccessFromFileURLs(true); // 本地文件能否通过ajax跨域访问http/https
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); // 允许https中加载http

注意:如果页面有 fixed 布局,为了保证 fix 布局在键盘弹起时不滚动,在键盘弹起前应该把 viewport 的 height 还原回去

总结

之前也适配了许多 android/ios 中关于 input 和 fixed 布局的问题,这次终于从系统层面上了解到了一些问题的原因

  1. 为什么 Android 对 fixed 布局支持的那么好

  2. 为什么在键盘弹起时 ios 的 fixed 布局无效

  3. 为什么 IOS 没有 resize 事件

参考

Android 爬坑之旅:软键盘挡住输入框问题的终极解决方案