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

相关推荐
xiaolizi56748940 分钟前
安卓远程安卓(通过frp与adb远程)完全免费
android·远程工作
阿杰1000144 分钟前
ADB(Android Debug Bridge)是 Android SDK 核心调试工具,通过电脑与 Android 设备(手机、平板、嵌入式设备等)建立通信,对设备进行控制、文件传输、命令等操作。
android·adb
梨落秋霜1 小时前
Python入门篇【文件处理】
android·java·python
遥不可及zzz4 小时前
Android 接入UMP
android
Coder_Boy_5 小时前
基于SpringAI的在线考试系统设计总案-知识点管理模块详细设计
android·java·javascript
冬奇Lab6 小时前
【Kotlin系列03】控制流与函数:从if表达式到Lambda的进化之路
android·kotlin·编程语言
冬奇Lab6 小时前
稳定性性能系列之十二——Android渲染性能深度优化:SurfaceFlinger与GPU
android·性能优化·debug
冬奇Lab7 小时前
稳定性性能系列之十一——Android内存优化与OOM问题深度解决
android·性能优化
用户74589002079548 小时前
线程池
android