Android 监听软键盘的高度并解决其覆盖输入框的问题
1、前言
在某些项目中,我们常常需要自定义一个输入框,软键盘弹出时就把输入框顶上去,关闭时输入框再回到原位(比如下方的效果图,实际上各种 App 中的聊天界面和发布评论的界面大体都是这样)。在这个过程中,除了输入框以外的其他界面的元素不受影响,比如效果图中的背景图片不会上移也不会被压缩。但在实际使用中发现软键盘在弹出时常常把输入框盖住,导致输入框显示不完全。有什么方法可以解决呢?
2、思路分析
2.1 获取软键盘的高度
网上常见的思路是这样的:在输入框的下面放置一个View
,当软键盘弹出时,获取软键盘高度,然后在代码中动态将该View
的高度设置成跟软键盘的一样,这样输入框就被它顶上去了。从视觉上来看,就像是被软键盘顶上去一样。
这个思路的难点在于准确获取软键盘的动态高度。Android 系统没有提供直接获取软键盘高度的 api,好在我们可以曲线救国:软键盘的高度其实就是屏幕高度减去软键盘上方的可见区域(即没有被软键盘挡住的区域)高度,也就是:
软键盘高度 = 屏幕高度 - 可见区域高度
此外,还需要考虑状态栏和虚拟导航栏高度,所以我们可以得出以下的计算公式:
软键盘高度 = 屏幕高度 - 可见区域高度 - 顶部状态栏高度 - 底部导航栏高度
不过有两点需要注意:
Activity 为全屏时是没有状态栏的,不必扣除高度;
横屏时虚拟状态栏是在侧边的,这时也不必扣除它的高度了。
最后我们的公式可以修正为:
软键盘高度 = 屏幕高度 - 可见区域高度 - 顶部状态栏高度(非全屏时) - 底部导航栏高度(竖屏时)
这个公式中的屏幕高度、状态栏高度和导航栏高度都可以通过 Android 的 api 获取,所以,现在问题的难点转换成了准确获取可见区域的动态高度。
2.2 获取可见区域高度
准确获取可见区域的动态高度,何为准确,何为动态呢?要想准确,我们必须要准确获取可见区域的对象,要想动态,那必须监听可见区域的高度变化,也即是:
获取可见区域(对应准确);
监听可见区域的高度变化(对应动态)。
首先来看第一步,View
类中为我们提供了一个方法getWindowVisibleDisplayFrame()
,它可以获取某个View
所在窗口(Window
)的可见区域(注意:是窗口的可见区域,不是View
的可见区域!)。它需要传入一个Rect
对象,从Rect
对象中,我们就可以获取到可见区域的信息,比如可见区域顶部距离父布局顶部的距离 top
和可见区域底部部距离父布局顶部的距离bottom
,两者一相减就是我们需要的可见区域高度了。
那么用哪一个View
来获取可见区域呢?当前Activity
或者Fragment
上面的布局或者控件吗?答案是不行的。因为Activity
(或Fragment
)跟软键盘是位于同一个窗口的,也就是说,软键盘也在这个窗口的可见区域内,无论软键盘弹出还是关闭,可见区域的大小都不会变化!
既然如此,那么我们就需要另外一个窗口了。有没有办法创建一个不属于软键盘所在窗口的View
呢?当然可以,Dialog
和PopupWindow
就可以办到。我们需要这个View
一直存在,便于监听,所以PopupWindow
无疑是最合适的。
第一步解决后,接下来就是监听可见区域的变化了这个比较简单,可以通过继承接口ViewTreeObserver.OnGlobalLayoutListener
来,在onGlobalLayout()
中监听来实现。
3、代码实践
思路已经捋清楚了,现在是代码时间。创建一个KeyboardStatusWatcher
类,继承于PopupWindow
和ViewTreeObserver.OnGlobalLayoutListener
接口:
class KeyboardStatusWatcher(private val activity: FragmentActivity,private val lifecycleOwner: LifecycleOwner,private val listener: (isKeyboardShowed: Boolean, keyboardHeight: Int) -> Unit
) : PopupWindow(activity), ViewTreeObserver.OnGlobalLayoutListener {private val rootView by lazy { activity.window.decorView.rootView }private val TAG = "Keyboard-Tag"/*** 可见区域高度*/private var visibleHeight = 0/*** 软键盘是否显示*/var isKeyboardShowed = falseprivate set/*** 最近一次弹出的软键盘高度*/var keyboardHeight = 0private set/*** PopupWindow 布局*/private val popupView by lazy {FrameLayout(activity).also {it.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.MATCH_PARENT)//监听布局大小变化it.viewTreeObserver.addOnGlobalLayoutListener(this)}}init {//初始化 PopupWindowcontentView = popupView//软键盘弹出时,PopupWindow 要调整大小softInputMode =WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE orWindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLEinputMethodMode = INPUT_METHOD_NEEDED//宽度设为0,避免遮挡界面width = 0height = ViewGroup.LayoutParams.MATCH_PARENTsetBackgroundDrawable(ColorDrawable(0))rootView.post { showAtLocation(rootView, Gravity.NO_GRAVITY, 0, 0) }//activity 销毁时或者 Fragment onDestroyView 时必须关闭 popupWindow ,避免内存泄漏lifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {override fun onDestroy(owner: LifecycleOwner) {super.onDestroy(owner)dismiss()}})}/*** 监听布局大小变化*/override fun onGlobalLayout() {val rect = Rect()//获取当前可见区域popupView.getWindowVisibleDisplayFrame(rect)if (visibleHeight == (rect.bottom - rect.top)) {//可见区域高度不变时不必执行下面代码,避免重复监听return} else {visibleHeight = (rect.bottom - rect.top)}//粗略计算高度的变化值,后面会根据状态栏和导航栏修正val heightDiff = rootView.height - visibleHeight//这里取了一个大概值,当窗口高度变化值超过屏幕的 1/3 时,视为软键盘弹出if (heightDiff > activity.screenHeight / 3) {isKeyboardShowed = true//非全屏时减去状态栏高度keyboardHeight =if (activity.isFullScreen) heightDiff else heightDiff - activity.statusBarHeight//导航栏显示时减去其高度,但横屏时导航栏在侧边,故不必扣除高度if (activity.hasNavBar && activity.isNavBarShowed && activity.isPortrait) {keyboardHeight -= activity.navBarHeight}} else {//软键盘隐藏时键盘高度为0isKeyboardShowed = falsekeyboardHeight = 0}listener.invoke(isKeyboardShowed, keyboardHeight)}
}
代码都是遵循前面的思路分析编写的,注释也比较详细,就不过多分析了。只要关注一下 PopupWindow
存在时软键盘的交互。PopupWindow
与软键盘分属于不同的窗口,软键盘弹出时,默认会被PopupWindow
覆盖的(你可以通过修改上面的代码,给PopupWindow
设置颜色且宽度不为 0 来验证),这样PopupWindow
的高度不发生变化,就无法达到监听的目的。所以我们需要设置softInputMode
和inputMethodMode
两个属性,让PopupWindow
的高度随着软键盘的弹出和关闭而调整。
然后简单看看MainActivity 布局:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/clRoot"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@drawable/watermelon"tools:context=".MainActivity"><Viewandroid:id="@+id/vKeyboardBg"android:layout_width="match_parent"android:layout_height="60dp"android:background="@android:color/white"app:layout_constraintBottom_toBottomOf="parent" /><androidx.appcompat.widget.AppCompatEditTextandroid:imeOptions="flagNoExtractUi"android:id="@+id/editText"android:layout_width="match_parent"android:layout_height="0dp"android:layout_marginHorizontal="15dp"android:layout_marginVertical="8dp"android:background="@drawable/shape_edit_bg"android:hint="请输入"android:paddingHorizontal="10dp"app:layout_constraintBottom_toBottomOf="@id/vEditBg"app:layout_constraintTop_toTopOf="@id/vEditBg" /></androidx.constraintlayout.widget.ConstraintLayout>
注意:这里EditText
要加上android:imeOptions="flagNoExtractUi"
属性,不然横屏时样式发生会变化。
还有,别忘了在清单文件中给Activity加上android:windowSoftInputMode="adjustNothing|stateHidden"
,否则软键盘弹出时布局会整体上移的。
最后当然是在Activity
中调用了:
KeyboardStatusWatcher(this,this) { isKeyboardShowed: Boolean, keyboardHeight: Int ->vKeyboardBg.updateLayoutParams<ConstraintLayout.LayoutParams> {bottomMargin = keyboardHeight}Log.d("Tag", "isShowed = $isKeyboardShowed,keyboardHeight = $keyboardHeight")}}
4、项目地址
文章到此就结束了,项目地址如下:Gitee。
项目还有一个不足之处:实现需求了,但是使用体验上跟微信相比差很多,微信的输入框在软键盘弹出和收起时上下移动非常顺滑,没有什么闪烁。
如果你有更好的实现方法或者有其他的批评建议,欢迎留言和我交流。
5、参考文章
android EditText横屏显示问题 - 简书
Android动态获取软键盘的高度,监听软键盘显示或则隐藏。 - 掘金
Android获取窗口可视区域大小: getWindowVisibleDisplayFrame()_ccpat的专栏-CSDN博客
Android全面解析之Window机制_一只修仙的猿-CSDN博客
Android 监听软键盘的高度并解决其覆盖输入框的问题相关推荐
- Android 监听软键盘按键的三种方式
前言: 我们在Android手机上面有时候会遇到监听手机软键盘按键的时候,例如:我们在浏览器输入url完毕后可以点击软键盘右下角的"Go"按键加载url页面:在点击搜索框的时候,点 ...
- Android监听软键盘开启关闭状态
最近公司项目有一个功能点是监听软键盘的打开关闭状态,然后来展开不同的布局,后来在StackOverFlow上面找到一位大神提供了解决办法,大致做法如下: 在你需要监听状态的界面activity或者fr ...
- android 软键盘 状态,Android监听软键盘状态
监听软键盘隐藏或显示,代码如下: /** * 监听软键盘状态 */ private void listenerInput() { final LinearLayout ll_main = (Linea ...
- android 键盘回车按钮事件,android 监听软键盘 回车键
InputMethodManager imm = (InputMethodManager)getActivity().getSystemService(Context.); imm.hideSoftI ...
- android监听键盘的隐藏,Android监听软键盘的显示和隐藏
使用步骤 xml 布局文件布局,和普通的控件一下 获取SoftInputCanListenerEditText 实例,并设置监听器 Activity 注册的时候android:windowSoftIn ...
- uni-app textarea解决点击软键盘上自带收起按钮输入框没有取消焦点的问题(监听软键盘自带收起按钮)
问题描述: 输入框弹起之后点击软键盘自带的收起按钮,发现输入框并没有取消焦点 问题导致: 再次点击输入框进行内容输入的时候软键盘弹起 输入框不显示 问题解决: <textarea cursor- ...
- android 键盘隐藏监听,安卓监听软键盘弹出与隐藏的两种方法
需求: 现在有一个需求是点击一行文本框,弹出一个之前隐藏的输入框,输入完成后按返回键或者其他的东西隐藏键盘和输入框,将输入框的内容填充到文本框中. 实现: 拿到这个需求的第一反应就是写一个监听来监听键 ...
- 【Android应用】【监听软键盘弹起与关闭】
[背景] 在很多App开发过程中需要在Activity中监听Android设备的软键盘弹起与关闭,但是Android似乎没有提供相关的的监听API给我们来调用,本文提供了一个可行的办法来监听软键盘的弹 ...
- Android App监听软键盘按键的三种方式(转)
最近有类似需求,在csdn上刚好发现,粘贴过来,以防止忘记喽 前言: 我们在android手机上面有时候会遇到监听手机软键盘按键的时候,例如:我们在浏览器输入url完毕后可以点击软键盘右下角的&quo ...
最新文章
- Linux 下的终端选择,以及剪切板配置
- Python input()
- java如何显示当天世界_Java学习笔记——显示当前日期的三种方式
- SAP CRM Survey调查问卷的模型设计原理解析
- maven安装后环境变量配置
- C# 编程规范 (coding standard)
- mybatis mysql 中文乱码_Mybatis + Mysql 插入数据时中文乱码问题
- vb.net学习笔记
- 纽微特纪事:改个字串,竟然成了“二期工作”,还拖了几个月
- idea切换工作空间_IntelliJIDEA使用技巧
- 小型软件企业组织结构
- Ardupilot移植经验分享(2)
- Modern PHP读书笔记一
- 在文件选择打开方式里,无法添加.exe程序怎么解决
- 精简压缩优化 Docker 镜像几百MB
- TensorFlow2 -官方教程 :保存和恢复模型
- HTML 显示系统时间
- 数据挖掘学习笔记:标称属性(名词性)的邻近性度量
- apk 路由器劫持_各种路由器固件劫持方法(待完善)
- 如何打开tdms文件?
热门文章
- 基于51的数码管电子时钟(显示时、分、秒)——定时器
- SpringToolSuit 保姆级安装教程
- uniapp开发微信小程序 wx.navigateBack()携带数据问题
- MySQL执行过程及执行顺序
- 计算机网考模拟系统,全国计算机等级考试考网模拟系统
- TiDB 简介(二)
- 性能测试TPS/QPS/RT理解
- BlockChain:2020年7月10日世界人工智能大会WAIC《链智未来 赋能产业区块链主题论坛》(四)
- 数论:最大公约数与最小公倍数的应用:最大公约数和最小公倍数问题
- 什么是Bom?有哪些常用的Bom属性?