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

相关推荐
Dingdangr3 小时前
Android中的Intent的作用
android
技术无疆3 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP3 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
Jouzzy10 小时前
【Android安全】Ubuntu 16.04安装GDB和GEF
android·ubuntu·gdb
极客先躯11 小时前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
Good_tea_h13 小时前
Android中的单例模式
android·单例模式
计算机源码社18 小时前
分享一个基于微信小程序的居家养老服务小程序 养老服务预约安卓app uniapp(源码、调试、LW、开题、PPT)
android·微信小程序·uni-app·毕业设计项目·毕业设计源码·计算机课程设计·计算机毕业设计开题
丶白泽19 小时前
重修设计模式-结构型-门面模式
android
晨春计20 小时前
【git】
android·linux·git
标标大人21 小时前
c语言中的局部跳转以及全局跳转
android·c语言·开发语言