Android — 通过ViewCompat和WindowInsetsCompat处理软键盘

在App的开发中,少不了与软键盘打交道,例如进入搜索页时主动显示软键盘、点击软键盘外区域主动收起软键盘等。本文介绍如何通过ViewCompatWindowInsetsCompat处理软键盘。

软键盘处理

判断软键盘是否显示

通过ViewCompatsetOnApplyWindowInsetsListener()方法,对当前WindowdecorView进行监听。在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
        }
    }
}

效果如图:

显示或隐藏软键盘

使用WindowInsetsControllerCompatshow()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())
        }
    }
}

效果如图:

触摸非软键盘与输入框区域隐藏软键盘

重写ActivitydispatchTouchEvent()方法,判断手指的落点是否在输入框控件区域内,不是的话隐藏软键盘。落在软键盘上的点击会被软键盘消耗掉,不会调用ActivitydispatchTouchEvent方法。具体代码如下:

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
        }
    }
}

效果如图:

无处理 添加处理

输入框被遮挡

有时候输入框可能在页面中的下半部分,甚至可能在底部,如果不做额外处理会被软键盘遮挡。

通过WindowsetSoftInputMode()方法,设置软键盘显示时通过平移窗口来保证焦点控件可见(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中添加。

ExampleDemo github

ExampleDemo gitee

相关推荐
- 羊羊不超越 -21 分钟前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
wk灬丨1 小时前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
千雅爸爸1 小时前
Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)
android
晨曦_子画2 小时前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
孤客网络科技工作室2 小时前
AJAX 全面教程:从基础到高级
android·ajax·okhttp
Mr Lee_3 小时前
android 配置鼠标右键快捷对apk进行反编译
android
顾北川_野4 小时前
Android CALL关于电话音频和紧急电话设置和获取
android·音视频
&岁月不待人&4 小时前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
Winston Wood6 小时前
Android Parcelable和Serializable的区别与联系
android·序列化