在App的开发中,少不了与软键盘打交道,例如进入搜索页时主动显示软键盘、点击软键盘外区域主动收起软键盘等。本文介绍如何通过ViewCompat
和WindowInsetsCompat
处理软键盘。
软键盘处理
判断软键盘是否显示
通过ViewCompat
的setOnApplyWindowInsetsListener()
方法,对当前Window
的decorView
进行监听。在OnApplyWindowInsetsListener
回调中,通过WindowInsetsCompat
获取软键盘的高度,代码如下:
kotlin
class KeyboardExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutKeyboardExampleActivityBinding
private var keyboardShown = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = LayoutKeyboardExampleActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, windowInsets ->
// 当窗口发生变化时,回调会被执行(例如系统状态栏变化、软键盘变化)
windowInsets.getInsets(WindowInsetsCompat.Type.ime()).run {
// 当软键盘显示时,bottom的值为软键盘的高度加导航栏的高度。软键盘隐藏时,bottom的值为0
binding.tvKeyboardHeight.text = "keyboard height: $bottom"
keyboardShown = bottom > 0
}
WindowInsetsCompat.CONSUMED
}
}
}
效果如图:
显示或隐藏软键盘
使用WindowInsetsControllerCompat
的show()
和hide()
方法,可以手动显示和隐藏软键盘,代码如下:
kotlin
class KeyboardExampleActivity : AppCompatActivity() {
......
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
......
binding.btnShowKeyboard.setOnClickListener { showKeyboard(binding.etInputData) }
binding.btnHideKeyboard.setOnClickListener { hideKeyboard(binding.etInputData) }
}
private fun showKeyboard(editText: View) {
if (!keyboardShown) {
editText.isFocusable = true
editText.isFocusableInTouchMode = true
editText.requestFocus()
WindowInsetsControllerCompat(window, editText).show(WindowInsetsCompat.Type.ime())
}
}
private fun hideKeyboard(editText: View) {
if (keyboardShown) {
editText.clearFocus()
WindowInsetsControllerCompat(window, editText).hide(WindowInsetsCompat.Type.ime())
}
}
}
效果如图:
触摸非软键盘与输入框区域隐藏软键盘
重写Activity
的dispatchTouchEvent()
方法,判断手指的落点是否在输入框控件区域内,不是的话隐藏软键盘。落在软键盘上的点击会被软键盘消耗掉,不会调用Activity
的dispatchTouchEvent
方法。具体代码如下:
kotlin
class KeyboardExampleActivity : AppCompatActivity() {
......
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
if (!isTouchTargetView(ev, binding.etInputData)) {
// 判断不是触摸输入框的位置则隐藏软键盘
currentFocus?.let { hideKeyboard(it) }
}
return super.dispatchTouchEvent(ev)
}
private fun isTouchTargetView(ev: MotionEvent?, view: View?): Boolean {
return if (ev != null && view != null) {
val screenLocation = IntArray(2)
view.getLocationOnScreen(screenLocation)
val viewX = screenLocation[0]
val viewY = screenLocation[1]
(ev.rawX >= viewX && ev.rawX <= (viewX + view.width)) && (ev.rawY >= viewY && ev.rawY <= (viewY + view.height))
} else {
false
}
}
}
效果如图:
无处理 | 添加处理 |
---|---|
输入框被遮挡
有时候输入框可能在页面中的下半部分,甚至可能在底部,如果不做额外处理会被软键盘遮挡。
通过Window
的setSoftInputMode()
方法,设置软键盘显示时通过平移窗口来保证焦点控件可见(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN
)。然后对比窗口可见区域和输入框的位置来判断是否需要进行处理,代码如下:
kotlin
class KeyboardExampleActivity : AppCompatActivity() {
private lateinit var binding: LayoutKeyboardExampleActivityBinding
private var keyboardShown = false
private var needRestore = false
......
private fun handleKeyboardObscuringView() {
if (keyboardShown) {
currentFocus?.let { focusView ->
val rect = Rect()
// 获取当前窗口的可见区域相对于页面根布局的位置和尺寸
binding.root.getWindowVisibleDisplayFrame(rect)
val locations = IntArray(2)
// 获取当前焦点控件在页面中的位置
focusView.getLocationInWindow(locations)
// 当前窗口可见区域的底部高度小于焦点控件的y轴坐标加上它的高度时
// 判定为软键盘已经遮挡住了控件
if (rect.bottom < locations[1] + focusView.height) {
// 如果输入框被软件盘遮挡,则滚动页面至可以完全显示输入框的位置
binding.root.scrollTo(0, (locations[1] + focusView.height + focusView.height / 2) - rect.bottom)
needRestore = true
}
}
} else if (needRestore) {
// 需要时回滚到页面的顶部
binding.root.scrollTo(0, 0)
}
}
}
效果如图:
无处理 | 添加处理 |
---|---|
示例
演示代码已在示例Demo中添加。