做过 Android 软键盘的或多或少都会踩的坑,adjustResize 和 adjusPan 有什么区别,为什么 adjustPan 会把当前页面顶出去?全屏模式下 adjustResize 怎么也顶出去了,如何适配不让当前页面顶出屏幕?
1、软键盘介绍
系统软键盘是由 InputMethodService
管理,它为软键盘创建了一个继承 Diaolog
的 SoftInputWindow
,设置显示在底部:
1 | mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, |
当点击输入框时,系统调整当前主窗口来显示底部的软键盘区域。那系统是根据什么策略来调整当前主窗口的空间来显示底部软键盘的呢?
系统对外提供了 windowSoftInputMode 字段,来控制主窗口为软键盘腾空间的策略,其属性有:
属性 | 含义 | 优缺点 |
---|---|---|
adjustResize | 调整Activity主窗口的尺寸来为屏幕上的软键盘腾出空间 | 优点:1、不会把标题栏顶出当前可见布局;2、有多项输入时,当前输入框下面的输入框可上下滑动输入. 缺点:1.需要界面本身可调整尺寸:要么使用滑动组件适配,要么自己在onLayout适配调整;2. 全屏时失效 |
adjustPan | 自动平移窗口的内容,使当前焦点永远不被键盘遮盖,让用户始终都能看到其输入的内容 | 优点:不会失效,界面整体往上平移. 缺点:1、会把标题栏顶出当前可见布局;2、有多项输入时,当前输入框下面的输入框无法输入,必须收起键盘显示输入框再输入 |
adjustUnspecified | 根据窗口是否存在任何可滚动的布局视图,来选择adjustResize或者adjustPan | 在不同模式下优缺点如上俩种 |
adjustNoting | 软键盘弹出时,主窗口Activity不会做出任何响应 | 主窗口不调整适配 |
adjust 系列的适配模式可用如下图来表达主窗口的调整适配:
2、软键盘遮挡及滑动问题
对于非全屏,我们使用 adjustResize
就能适配一般情况下的软键盘弹出场景,但对于以下情况还需要额外进行适配:
- 非全屏模式下,软键盘弹出时遮挡住了需要显示的非输入焦点区域
- 全屏模式下,
adjustResize
无效,同时不想顶部标题栏被顶上去不见
那适配方式一般有如下俩种解决方案:
1、解决方案一:adjustResize
非全屏使用 adjustResize
,系统会自动调整主窗口的布局,所以可以有多种方式来适配:
- 嵌套滚动布局,对于局部可以滚动适配的,可以把输入框放在
ScrollView
下 - 监听主窗口的
onLayoutChange
变化,在软键盘弹出时平移被遮挡的显示区域
例如在面板根布局下监听 onLayoutChange
,获取弹出前后的位置,再添加被遮挡区域的高度进行绘制。
1 | panelView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { |
对于卡噗的全屏模式来说,adjustResize
失效 ,系统仍然会平移当前窗口把标题栏顶上去,而不会让主窗口调整。
所以需要使用方案二的 onGlobalLayoutListener
全局监听来解决标题栏被顶出去的问题。
2、解决方案二:onGlobalLayoutListener
OnGlobalLayoutListener
是 ViewTree
的全局布局状态或可见性发生变化时的监听,我们可以在软键盘弹出/消失触发主窗口的全局布局变化监听里,改变主窗口不把标题栏顶出去。
不把顶部标题栏顶出去,就是要重新设置主窗口的布局,有以下几种方式可以做到:
- scrollTo:平移
rootView
,平移高度 = 输入法高度 - layout:改变
rootView
的height
,使view
的高度 = 屏幕高度 - 输入法高度 - padding:改变
rootView
的padding
,使底部边距 = 输入法高度
经过每种方法验证,最终发现是 padding 方案最适合。所以这里通过设置 rootView
的底部间距,来调整主窗口正好在输入法窗口之上。
1 | private ViewTreeObserver.OnGlobalLayoutListener mRootViewListener = new ViewTreeObserver.OnGlobalLayoutListener() { |
适配情况如下所示:
3、其他问题
1、部分机型兼容性问题,无 onGlobalLayoutListener
回调
在使用 onGlobalLayoutListener
方法时,部分低端机型上没有回调,导致标题栏仍然被顶上去。尝试的解决方案有:
- 手动消除
EditText
的焦点,在键盘收起时,EditText
重获焦点后,触发onGlobalLayoutListener
回调。但带来的问题可能是:回调在主窗口已被顶上去之后,此时再调整时下来窗口会闪动。
关于更多可能尝试的解决方案的,可以参考:https://stackoverflow.com/questions/7417123/android-how-to-adjust-layout-in-full-screen-mode-when-softkeyboard-is-visible 。
2、 onGlobalLayoutListener
无法释放
在 Fragment
的 onDestroy
对 onGlobalLayoutListener
移除时无效,退出后仍然能收到监听。这里应该是在Fragment
的 onDetach
后移除的无效,建议在 Fragment
的 onDestroyView
中移除。