说起Android软键盘,这里遇到的坑多数就是对它理解不够全面导致的,所以决定好好总结一下它。在避免自己二次掉坑的同时,如果能帮助到在这个知识区正疑惑的你,那就太好了。本篇的讲解将分以下几个主题:
1、操作软键盘
2、监听键盘的弹起与隐藏
3、如何避免键盘遮挡输入框
1 操作软键盘
想要操作软键盘,需要使用到 InputMethodManager ,它是一个系统服务,可以使用 Context.getSystemService() 获取到它。
1.1 显示软键盘
在 InputMethodManager中,有两个方法showSoftInput()和showSoftInputFromInputMethod(),而实际上,只有showSoftInput()是有效的。
这里我们只需要传递两个参数。它首先需要一个View ,使用软键盘就是为了输入,而输入就需要有接收输入内容的View ,这里接收输入的View ,最好是一个EditText(但这不是必须的)。
而第二个参数flags就是个标志位,从上面截图的方法签名上的文档上可以看到,它接收0或者SHOW_INPYT_IMPLICIT 两个参数,但是实际上,它有第三个参数,另外一个是SHOW_FORCED。一般没有特别需要的话,我们直接传递0就好了(实际上SHOW_INPYT_IMPLICIT、SHOW_FORCED并不影响显示,只是在隐藏的时候,会有一些限制。而且我们一般的话,不管显示或者隐藏都将flag设置为0,特殊情况遇到再做分析)。
现在,简单总结一下调用showSoftInput()会生效的关键点:
1、第一个参数,最好是EditText或者它的子类。
考虑到软键盘就是为了输入,EditText就是一个接收输入的控件。而这不是绝对的,如果不是一个EditText ,就必须要求这个View有两个属性,分别是:android:focusable=”true” 和android:focusableInTouchMode=”true”。
2、第一个参数,必须是可获取焦点的,并且当前已经获取到焦点。
EditText默认是允许获取焦点的,但是假如布局中,存在多个可获取焦点的控件,就需要提前让我们传递进去的View获取到焦点。获取焦点可以使用requestFocus()方法。
3、布局必须加载完成。
在onCreate()中,如果立即调用showSoftInput()是不会生效的。想要在页面一启动的时候就弹出键盘,可以在Activity上,设置 android:windowSoftInputMode属性来完成,或者做一个延迟加载,View.postDelayed()也是一个解决方案。
所以最终,完整的显示软键盘的代码就如下所示了。
1.2 隐藏软键盘
虽然showSoftInput()方法是有效的,但是想要隐藏软键盘,就没有提供对应的hideSoftInput()方法,但是却有一个hideSoftInputFromWindow()方法,可以用来隐藏软键盘。
先来看看这个方法的签名,它同样有两个方法可以调用。
它接收两个参数,第一个参数是一个IBinder ,可以直接传递一个 View.getWindowToken()的windowToken对象就可以了。而第二个参数,就是隐藏软键盘的标志位,如果没有特殊要求的话,直接传递0就好了。
注意:这里虽然原则上需要传递一个之前弹出键盘传递的时候,传递的View的windowToken,但是实际情况是你只需要传递一个存在于当前布局ViewTree中,随意一个View的windowToken就可以了。
最终隐藏软件的代码就是这样的。
1.3 切换键盘的弹出和隐藏
在InputMethodManager中,还提供了一个toggleSoftInput()方法,如同它的名字一样,它可以让软键盘在显示和隐藏之间切换。
该方法,接收两个flags ,分别是控制show和hide时候的标识,它们的含义和前面介绍的showSoftInput()和hideSoftInputFromWindow()一致,所以没有特殊要求,直接传递0就好了。
toggleSoftInput()方法不要求传递一个View或者windowToken,所以它并没有showSoftInput()中的一些限制,但是依然还有需要在布局绘制完成之后调用才会有效果。
虽然这个方法,限制很少,但是我们基本上不会使用它。主要原因在于,它是一个开关的方法,会根据当前的状态做相反的操作。这就导致很多时候,我们在代码中,无法直接根据InputMethodManager提供的方法判断当前软键盘的显示状态,这样也就无法确定调用它的时候的效果了。
2 监听键盘的弹起与隐藏
如果想要监听键盘的弹出和收起,可以使用ViewTreeObserver.OnGlobalLayoutListener这个监听,来监听布局的调整,从而判断出键盘的弹出和隐藏。下面是一个使用ViewTreeObserver.OnGlobalLayoutListener后获取屏幕中各个类型高度的例子,请注意代码中的注释:
1 | getWindow().getDecorView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { |
代码中r.bottom - r.top代表了除去状态栏、底部导航栏后,手机屏幕剩余可视高度。注意,这个剩余可视高度也不包括输入法弹窗的高度(如果屏幕中有输入法弹窗的话)。有了以上的基础,我们就可以通过getWindow().getDecorView().getHeight()-r.bottom>100?键盘弹出:键盘隐藏来判断键盘是否隐藏了。
注意:为什么要大于100呢?因为如果手机具备底部导航区域的话,要排除这个值的干扰。当然了,直接默认底部导航区域高度为100肯定是不精确的,实际应用中我们应该获取到真实的底部导航区域高度,具体如何获取底部导航区域高度可以参考这篇文章。
3 如何避免键盘遮挡输入框
在开发中,经常会遇到键盘挡住输入框的情况,比如登录界面或注册界面,弹出的软键盘把登录或注册按钮挡住了,用户必须把软键盘收起,才能点击相应按钮,这样的用户体验非常不好。下面对几种在开发中常用的避免键盘遮挡输入框的方法进行总结。
3.1 方法一:windowSoftInputMode:adjustResize和adjustPan
主要实现方法:在 AndroidManifest.xml 对应的Activity里添加 android:windowSoftInputMode=”adjustPan” 或是 android:windowSoftInputMode=”adjustResize”属性
这两种属性的区别,官方的解释是:
这两个属性作用都是为了调整界面使键盘不挡住输入框,这里对这两种属性使用场景、优缺点、注意事项进行了全方面总结。对于fitsystemwindows还比较懵逼的你,可以参考这篇文章。
最后附一张官方的截图。
插播知识:
这里再提一下clipToPadding和clipChildren这两个属性吧,虽然和本篇没有关系但是同样是有一定概率出现的,所以要讲一下。clipToPadding就是说控件的绘制区域是否在padding里面的,true的情况下如果你设置了padding那么绘制的区域就往里缩,clipToPadding主要应用在listview头部和尾部设置padding后希望可以在滑动item时能滑动到padding区域。clipChildren表示是否限制子View在所属父View的范围内,我们将其值设置为false后那么当子控件的高度高于父控件时也会完全显示,而不会被压缩。注意clipChildren一定是在布局文件的根节点设置,否则不起作用,具体的使用请看这篇文章以及这篇文章。注意:有时候clipChildren会不起作用,如果确定考虑到了之前两篇文章所提及的内容,则应该想其它的办法替代clipChildren,这可以认为是clipChildren的BUG。
3.2 方法二:当键盘弹起时,让界面整体上移;键盘收起,让界面整体下移
使用场景:针对界面全屏模式,输入框不会被键盘遮挡。主要用于一些登录界面,或是需要把界面整体都顶上去的场景。
3.2.1 主要实现步骤
(1). 获取Activity布局xml的最外层控件,如xml文件如下:
1 | <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
先获取到最外层控件:
RelativeLayout main = (RelativeLayout) findViewById(R.id.main);
(2). 获取到最后一个控件,如上面的xml文件,最后一个控件是Button:
Button login_btn = (Button) findViewById(R.id.login_btn);
(3). 给最外层控件和最后一个控件添加监听事件:
1 | //在Activity的onCreate里添加如下方法 |
3.2.2 实现原理
此方法通过监听 Activity 最外层布局控件来检测软键盘是否弹出,然后去手动调用控件的scrollTo方法达到调整布局目的。