软键盘可能引发的问题:
- 键盘弹出,可能会挤压页面中的布局和view,导致页面显示不可控,尤其是当页面设置了各种主题的时候,更加不可控制;
- 处理软键盘和表情键盘切换,很容易造成切换生硬,有闪烁的问题;
传统解决方案的缺陷:
网上有很多解决方案,大多都是设置 android:windowSoftInputMode
=
"adjustResize|stateHidden"
,并把输入框靠近底部,这样就能通过窗口自适应把相应的输入控件挤上去,而且可以通过布局变化监听到软键盘弹出和消失事件,方便处理回调;
关于更多windowSoftInputMode等设置,请自行百度;
但是本人通过上述方法试过之后,发现有以下缺陷:
- 设置了
adjustResize,不只会影响输入控件,是对布局中所有控件都有影响,比如说一张自适应图片,就可能会被挤压变形;而且如果有表情键盘的情况下,表情键盘也会被挤压上去,切换时只能将表情键盘设置为不可见,这样就会造成切换闪烁问题;
- 页面嵌套设置主题影响: 如果当前页面嵌套在其他页面中,或者其他页面嵌套当前页面, 如果在外层或内层由于需求代码手动修改了键盘弹出模式,那么键盘弹出的表现将会出现不可控的情况;
- 如果多个页面都要用到输入控件,那么需要在每个页面中增加适配代码,与页面耦合严重,不利于封装;
新的解决方式:
我门在项目里使用了一种新方案,解决了上述问题; 大致方案如下:
第一步: 把输入控件抽离到单独的activity中;
为了解决由于外层页面影响键盘模式, 我门把输入控件放到单独的activity里面,当然,这个activity是透明的,他的主题设置为:
<style name="inputStyle" parent="UiKit.Light.Translucent">
<item name="android:statusBarColor">@android:color/transparent</item>
<!-- 半透明 -->
<item name="android:windowIsTranslucent">true</item>
<!-- 无标题 -->
<item name="android:windowNoTitle">true</item>
<!-- 背景透明 -->
<item name="android:windowBackground">@android:color/transparent</item>
</style>
同时,我们设置 android:windowSoftInputMode
=
"adjustNothing",这样固定页面中所有元素,不会跟随软件盘弹起,后面我们自己设置键盘的高度; 里面有点击空白消失的逻辑以及发送消息后的回调等等,那些就自己处理吧;
第二步: 监听键盘弹出;
由于我们设置了键盘弹出模式为adjustNothing,所以传统的监听布局变化的代码不会被触发,即
decorView?.viewTreeObserver?.addOnGlobalLayoutListener(mGlobalLayoutListener)中的listener不会被调用, 我们需要一个新的方法来监听, 思路是开启一个看不见的popupWIndow,宽度为0,高度为MATCH_PARENT,通过它的
OnGlobalLayoutListener来监听.工具类代码如下:
class SoftKeyboardHeightProvider : PopupWindow, ViewTreeObserver.OnGlobalLayoutListener {
var rootView: View? = null
var mActivity: Activity? = null
var heightMax: Int = 0
var listener: KeyboardListener? = null
var lastKeyboardHeight = -1
var isFirstLayout = true
constructor(context: Activity) : super(context) {
mActivity = context
rootView = View(context)
contentView = rootView
rootView?.viewTreeObserver?.addOnGlobalLayoutListener(this)
//设置宽度为0,高度为全屏
width = 0
height = ViewGroup.LayoutParams.MATCH_PARENT
//设置键盘弹出方式
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED
}
fun init() {
if (!isShowing) {
var decorView = mActivity?.window?.decorView ?: return
decorView.post {
showAtLocation(decorView, Gravity.NO_GRAVITY, 0, 0)
}
}
}
override fun onGlobalLayout() {
var rect = Rect()
rootView?.getWindowVisibleDisplayFrame(rect)
if (rect.bottom > heightMax) {
heightMax = rect.bottom
}
var keyboardHeight = heightMax - rect.bottom
if (listener != null && keyboardHeight != lastKeyboardHeight && !isFirstLayout) {
listener?.onHeightChanged(keyboardHeight)
}
lastKeyboardHeight = keyboardHeight
isFirstLayout = false
}
interface KeyboardListener {
fun onHeightChanged(heigt: Int)
}
}
使用如下:
var provider = SoftKeyboardHeightProvider(this)
provider.init()
provider.listener = object : SoftKeyboardHeightProvider.KeyboardListener {
override fun onHeightChanged(heigt: Int) {
// 键盘高度变化
}
}
第三步: 处理表情和键盘切换
这里每一种实现的控件可能有所不同,但是要保证表情和键盘切换顺滑,有几个要点:
1.保证表情和键盘高度一样,这个可以在第一次显示软键盘时记录高度,并设置给表情键盘; 如果首次显示表情键盘,此时软键盘高度未获取,那么此时可以高度不一致;
2.切换软键盘时表情键盘不做消失处理,只让软键盘覆盖住表情键盘;即表情键盘始终存在;
最终完成后的demo效果如下: