Android 一个比较完善的输入法状态监听 KeyBoardWatcher 实现
Android 一个比较完善的软键盘状态监听 KeyBoardWatcher 实现
相信大家都会遇到项目中有EditText控件需要监听输入法软键盘状态的需求
然后你会发现在查找API的时候,并没有这样的监听接口以及靠谱的API
不靠谱的方案:
方案1:
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).isActive
isActive方法是不正确的,看下源码注释 Return true if any view is currently active in the input method. 如果当前view获得焦点则返回true,意思就是说只要edittext获得焦点就返回true,但是你会发现当你关闭输入法的时候edittext还是处于获取焦点状态此方法返回的还是true。
方案2:
return context.window.attributes.softInputMode == WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
softInputMode是软键盘的Mode,并不是软键盘的状态status,这是API的理解错误。SOFT_INPUT_STATE_VISIBLE的源码注释读一下就明白了。它的值等同于你在xml里面设置设置android:windowSoftInputMode="stateVisible"这样的属性。SoftInputModeFlags类可以看下
如何曲线救国
使用ViewTreeObserver监听视图变化 如果屏幕高度-视图高度>0 则输入法弹出
这个方案仅适用单屏设备。因为我们知道定制的Android设备是有可能双屏的。输入法如果弹在副屏此方案不适用的。
针对此方案写了工具类。
package com.okay.commonbusiness.utils
import android.content.Context
import android.graphics.Rect
import android.os.Build
import android.util.Pair
import android.view.View
import android.view.ViewTreeObserver
/**
*
* @author Liang Jx
*
* @since 2019/8/5 11:36 AM
* @version ${VERSION}
* @desc
*
*/
class KeyBoardWatcher private constructor() {
private var globalLayoutListener: GlobalLayoutListener? = null
private var context: Context? = null
private var decorView: View? = null
interface OnKeyboardStateChangeListener {
/**
* 监听键盘状态变化监听
* @param isShow 是否显示
* @param heightDifference 界面变化的高度差
*/
fun onKeyboardStateChange(isShow: Boolean, heightDifference: Int)
}
/**
* 监听键盘的状态变化
* @param context
* @param decorView
* @param listener
* @return
*/
fun init(context: Context, decorView: View, listener: OnKeyboardStateChangeListener): KeyBoardWatcher {
this.context = context
this.decorView = decorView
this.globalLayoutListener = GlobalLayoutListener(listener)
addSoftKeyboardChangedListener()
return this
}
/**
* 释放资源
*/
fun release() {
removeSoftKeyboardChangedListener()
globalLayoutListener = null
}
/**
* 取消软键盘状态变化监听
*/
private fun removeSoftKeyboardChangedListener() {
if (globalLayoutListener != null && null != decorView) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
decorView!!.viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener)
} else {
decorView!!.viewTreeObserver.removeGlobalOnLayoutListener(globalLayoutListener)
}
}
}
/**
* 注册软键盘状态变化监听
*/
private fun addSoftKeyboardChangedListener() {
if (globalLayoutListener != null && null != decorView) {
removeSoftKeyboardChangedListener()
decorView!!.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
}
}
/**
* 判断键盘是否显示
* @param context
* @param decorView
* @return
*/
fun isKeyboardShowing(context: Context?, decorView: View): Pair<Boolean, Int> {
val outRect = Rect()
//指当前Window实际的可视区域大小,通过decorView获取到程序显示的区域,包括标题栏,但不包括状态栏。
decorView.getWindowVisibleDisplayFrame(outRect)
val displayScreenHeight = DisplayMetricsUtils.getDisplayScreenHeight(context!!)
//如果屏幕高度和Window可见区域高度差值大于0,则表示软键盘显示中,否则软键盘为隐藏状态。
val heightDifference = displayScreenHeight - outRect.bottom
return Pair(heightDifference > 0, heightDifference)
}
fun isKeyboardShow(context: Context?, decorView: View):Boolean{
return isKeyboardShowing(context,decorView).second>0
}
inner class GlobalLayoutListener(private val onKeyboardStateChangeListener: OnKeyboardStateChangeListener?) :
ViewTreeObserver.OnGlobalLayoutListener {
private var isKeyboardShow = false
init {
this.isKeyboardShow = false
}
override fun onGlobalLayout() {
if (null != onKeyboardStateChangeListener && null != decorView) {
val pair = isKeyboardShowing(context, decorView!!)
if (pair.first) {
onKeyboardStateChangeListener!!.onKeyboardStateChange(isKeyboardShow, pair.second)
isKeyboardShow = true
} else if (isKeyboardShow) {
onKeyboardStateChangeListener!!.onKeyboardStateChange(isKeyboardShow, pair.second)
isKeyboardShow = false
}
}
}
}
companion object {
fun get(): KeyBoardWatcher {
return KeyBoardWatcher()
}
}
}
注意: 在使用这个方案之后,偶发现它有个难受的问题,我知道如果不remove globalLayoutListener监听,只要你的视图子view有任何的更新,都会导致回调触发,这样你在监听里面做的操作就会频繁刷新。因为项目需求里有在软键盘弹出布局自动scrooltoY的需求,这样会导致手动滚动的时候会自动回弹。为了解决这个问题我在回调里面加了个参数isKeyboardShow,结合isKeyboardShow缓存的状态就可以完美监听软键盘的弹出了。
如何关闭收起软键盘
不做赘述,上代码
/**
* 关闭输入法
*
* @param context
* @param editText
*/
fun softInputHidden(context: Activity) {
try {
val softInputManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
softInputManager.hideSoftInputFromWindow(context.window.peekDecorView().windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
} catch (e: Exception) {
e.printStackTrace()
}
}
/**
* 显示输入法
*
* @param context
*/
fun softInputShow(context: Context) {
val softInputManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
softInputManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS)
}
这里注意token我是从activity获取的 ,你从view获取也可以,但是就需要多传入view参数
如何控制EditText密文和明文切换
/**
* 设置密码 显示和 隐藏
*
* @param editeText
* @param b
*/
fun changePwdVisiable(editeText: EditText, b: Boolean) {
if (b) {
// 显示密码
// editeText.transformationMethod = HideReturnsTransformationMethod.getInstance()
editeText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
} else {
// 隐藏密码
// editeText.transformationMethod = PasswordTransformationMethod.getInstance()
editeText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
}
Selection.setSelection(editeText.text, editeText.text.length)
editeText.requestFocus()
}
两种方案,注释的代码是第二种方案,我更喜欢inputType的方案,因为我可以设置text类型。他俩的显示效果有点不太一样。
好啦!结束!经常总结这些工具类,对开发效率有很大的帮助。避免用时百度到一堆坑。