最近终于总结出最佳的软键盘高度监测方案了,特此分享出来。
源码在此:KeyboardObserver.kt
视觉效果
当开启showDebug时候,可以看到这样的可视化的键盘高度监测。
源码分析
简单来说,这里说通过两个PopupWindow来实现的键盘高度测量。一个用于测量当前屏幕状态可绘制区域的最大高度,一个用于跟随键盘移动,进而通过高度差,算出键盘的高度。我们这里将前者称为RulerPopWin, 后者称为CursorPopWin。他们的代码分别如下:
1 | // RulerPopWin |
1 | // CursorPopWin |
这两个PopupWindow,除了contentView不同以外,还有两个属性不同,cursorPopWin多了两个属性
1 | softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE |
这两个属性,使得cursorPopWin高度会随着软键盘的弹出而变化。
当开启监听时,为cursorPopWin.contentView设置一个OnLayoutChangeListener,用于监听其布局变化。
1 | fun watch() { |
1 | private val cursorLayoutChangeListener = OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> |
在cursorLayoutChangeListener中,监听到cursotPopWin的变化后,再显示rulerPopWin。为什么要这么做呢?
因为在实践中,软键盘的变化,触发了onLayoutChange方法,如果是在这之前就把rulerPopWin显示出来,在某些机型或者系统版本中,也会出现rulerPopWin跟随键盘改变尺寸的情况。所以要将rulerPopWin显示在键盘弹出之后。
真实的键盘高度监听,实际上说在rulerLayoutChangeListener中。
1 | private val rulerLayoutChangeListener = object : OnLayoutChangeListener { |
OK,这就是全部关键逻辑了。
Q&A
为什么要通过rulerPopWin来获取高度,用其他获取屏幕高度的方法不好吗?
不可以,这里rulerPopWin,实际上是测量当前状态下,键盘收起时的最底部。这个状态受到很多其他方面的影响,比如横竖屏切换、底部导航条的显示或者隐藏。如果通过直接获取屏幕高度的方式,并不能与状态严格对其。尤其说底部导航条的各种显示模式,导致键盘收起的0线也是变化的。
为什么不只用一个cursorPopWin的软键盘弹起前后进行差值计算高度呢?
这样做在实践中也是不可行的。同样跟问题1中的场景类似,由于底部导航条各种显示模式的影响,导致软键盘0线是不确定的,而软键盘的弹起与收回,可能对应着不同的导航条显示模式,也就对应着不同的0线,这样计算出来的软键盘高度,很容易把导航条的高度也算进去。
由问题2想到,把导航条的高度减掉不就是键盘的高度了吗?
这样做理论上可行,但是实践上问题会很多。首先,你要针对不同的导航条模式做不同策略;其次,不同系统的导航条的高度不同,包括说传统3键导航条还是全面屏手势;再次,目前获取导航条高度,并没有一个完美的方案,有些系统下获取到的高度,跟实际高度是不相符的。
Author: boybeak