方法一:windowSoftInputMode:adjustResize和adjustPan
主要实现方法:在 AndroidManifest.xml 对应的Activity里添加 android:windowSoftInputMode=”adjustPan” 或是 android:windowSoftInputMode=”adjustResize”属性
活动的主窗口如何与包含屏幕上的软键盘窗口交互。这个属性的设置将会影响两件事情 :
1> 软键盘的状态——是否它是隐藏或显示——当活动 (Activity)成为用户关注的焦点。
2> 活动的主窗口调整——是否减少活动主窗口大小以便腾出空间放软键盘或是否当活动窗口的部分被软键盘覆盖时它的内容的当前焦点是可见的。
它的设置必须是下面列表中的一个值,或一个 ”state…”值加一个 ”adjust…”值的组合。在任一组设置多个值——多个 ”state…”values,例如& mdash有未定义的结果。各个值之间用 |分开。例如 : <activity android:windowSoftInputMode="stateVisible|adjustResize" . . . >
在这设置的值 (除 "stateUnspecified"和 "adjustUnspecified"以外 )将覆盖在主题中设置的值
描述 | |
"stateUnspecified" | 软键盘的状态 (是否它是隐藏或可见 )没有被指定。系统将选择一个合适的状态或依赖于主题的设置。 这个是为了软件盘行为默认的设置。 |
"stateUnchanged" | 软键盘被保持无论它上次是什么状态,是否可见或隐藏,当主窗口出现在前面时。 |
"stateHidden" | 当用户选择该 Activity时,软键盘被隐藏——也就是,当用户确定导航到该 Activity时,而不是返回到它由于离开另一个 Activity。 |
"stateAlwaysHidden" | 软键盘总是被隐藏的,当该 Activity主窗口获取焦点时。 |
"stateVisible" | 软键盘是可见的,当那个是正常合适的时 (当用户导航到 Activity主窗口时 )。 |
"stateAlwaysVisible" | 当用户选择这个 Activity时,软键盘是可见的——也就是,也就是,当用户确定导航到该 Activity时,而不是返回到它由于离开另一个Activity。 |
"adjustUnspecified" | 它不被指定是否该 Activity主 窗口调整大小以便留出软键盘的空间,或是否窗口上的内容得到屏幕上当前的焦点是可见的。系统将自动选择这些模式中一种主要依赖于是否窗口的内容有任何布局 视图能够滚动他们的内容。如果有这样的一个视图,这个窗口将调整大小,这样的假设可以使滚动窗口的内容在一个较小的区域中可见的。这个是主窗口默认的行为 设置。 |
"adjustResize" | 该 Activity主窗口总是被调整屏幕的大小以便留出软键盘的空间 |
"adjustPan" | 该 Activity主窗口并不调整屏幕的大小以便留出软键盘的空间。相反,当前窗口的内容将自动移动以便当前焦点从不被键盘覆盖和用户能总是看到输入内容的部分。这个通常是不期望比调整大小,因为用户可能关闭软键盘以便获得与被覆盖内容的交互操作。 |
-
adjustResize失效情况:activity 设置了全屏属性指 Theme.Light.NotittleBar.Fullscreen 或者设置了 activity 对应的主题中 android:windowTranslucentStatus 属性,设置方式为:android:windowTranslucentStatus=true,这时如果对应的页面上含有输入框,将会导致点击输入框时软键盘弹出后键盘覆盖输入框,导致输入框看不见。
-
fitsSystemWindows=”true”,只有初始的view起作用:如果在布局中不是最外层控件设置 fitsSystemWindows=”true”, 那么设置的那个控件高度会多出一个状态栏高度。若有多个view设置了,因第一个view已经消耗掉 insect,其他 view 设置了也会被系统忽略
1、adjustPan
整个界面向上平移,使输入框露出,它不会改变界面的布局;界面整体可用高度还是屏幕高度,这个可以通过下面的截图看出,如点击 输入框6,输入框会被推到键盘上方,但 输入框1 被顶出去了,如果界面包含标题栏,也会被顶出去
2、adjustResize
需要界面的高度是可变的,或者说 Activity 主窗口的尺寸是可以调整的,如果不能调整,则不会起作用。
例如:Activity 的xml布局中只有一个 LinearLayout 包含若干EditText,在Activity的 AndroidMainfest.xml 中设置 android:windowSoftInputMode=”adjustResize” 属性,点击 输入框6, 发现软键盘挡住了 输入框6,并没有调整
但使用这两种属性,我们可以总结以下几点:
1). 使用 adjustPan, 如果需要输入的项比较多时,点击输入框,当前输入项会被顶到软键盘上方,但若当前输入框下面还有输入项时,却需要先收起键盘,再点击相应的输入项才能输入。这样操作太繁琐了,对于用户体验不大好;
2). adjustResize 的使用,需要界面本身可显示的窗口内容能调整,如果不能,不起作用;
方法二:在界面最外层布局包裹ScrollView
1、只使用ScrollView
在相应界面的xml布局中,最外层添加一个 ScrollView,不在 AndroidMainfest.xml 中设置任何 android:windowSoftInputMode属性,此时点击输入框,输入框均不会被软键盘档住。即使当前输入框下方也有输入框,在键盘显示的情况下,也可以通过上下滑动界面来输入,而不用先隐藏键盘,点击下方输入框,再显示键盘输入。
2、ScrollView + adjustPan
我们再在该类的 AndroidMainfest.xml 中设置 windowSoftInputMode属性 为 adjustPan:
发现当前输入框不会被挡住,但是输入框比较多时,在有键盘显示时,界面上下滑动,但只能滑动部分,且如果输入框在界面靠下方时,点击输入框,标题栏也会被顶出去
3、ScrollView+adjustResize
我们前面说过 adjustResize 的使用必须界面布局高度是可变的,如最外层套个 ScrollView 或是界面可收缩的,才起作用。这里在该类的 AndroidMainfest.xml 中设置windowSoftInputMode属性 为 adjustResize
发现效果和 1 不设置任何 windowSoftInputMode属性 类似,其使用高度也是:屏幕高度-状态栏高度-软键盘高度
可以看出,系统将选择合适的状态,也就是在界面最外层包含一层 ScrollView 时,设置默认属性值 stateUnspecified 其实就是 adjustResize属性。
但以下两方面无法满足需求:
1). 当 Activity 设置成全屏 fullscreen 模式时或是使用沉浸式状态栏时,界面最外层包裹 ScrollView,当输入框超过一屏,当前输入框下面的输入框并不能上下滑动来输入,情况类似于 ScrollView+adjustPan,只能滑动部分,通过 Inspect Layout 也可以看到,界面可用高度是整个屏幕高度,并不会进行调整高度。即使设置 adjustResize,也不起作用。
2). 如果是类似于注册界面或是登录界面,键盘会挡住输入框下面的登录按钮。
沉浸式状态栏下
自android系统4.4(API>=19)就开始支持沉浸式状态栏,当使用 System windows(系统窗口)显示系统一些属性和操作区域,如最上方的状态及没有实体按键的最下方的虚拟导航栏。
android:fitsSystemWindows=“true”会使得屏幕上的可布局空间位于状态栏下方与导航栏上方。
方法三:当键盘弹起时,让界面整体上移;键盘收起,让界面整体下移
使用场景:针对界面全屏或是沉浸式状态栏,输入框不会被键盘遮挡。主要用于一些登录界面,或是需要把界面整体都顶上去的场景。
1、主要实现步骤
(1). 获取Activity布局xml的最外层控件,如xml文件如下:
先获取到最外层控件:
RelativeLayout main = (RelativeLayout) findViewById(R.id.main);
(2). 获取到最后一个控件,如上面的xml文件,最后一个控件是Button:
Button login_btn = (Button) findViewById(R.id.login_btn);
(3). 给最外层控件和最后一个控件添加监听事件:
2、实现原理
此方法通过监听 Activity 最外层布局控件来检测软键盘是否弹出,然后去手动调用控件的 scrollTo方法 达到调整布局目的。
方法四:监听Activity顶层View,判断软键盘是否弹起,对界面重新绘制
此方法的实现来自android中提出的issue 5497
https://code.google.com/p/android/issues/detail?id=5497
使用场景:针对界面全屏或是沉浸式状态栏,界面包含比较多输入框,界面即使包裹了一层 ScrollView,在键盘显示时,当前输入框下面的输入不能通过上下滑动界面来输入。
一、实现步骤
1、把 SoftHideKeyBoardUtil类 复制到项目中;
2、在需要使用的Activity的onCreate方法中添加 SoftHideKeyBoardUtil.assistActivity(this) 即可。
二、实现原理
SoftHideKeyBoardUtil类 具体代码如下:
它的实现原理主要是:
(1). 找到 Activity 的最外层布局控件,我们知道所有的 Activity 都是 DecorView,它就是一个 FrameLayout控件,该控件id是系统写死叫 R.id.content,就是我们 setContentView 时,把相应的 View 放在此 FrameLayout 控件里
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
所以 content.getChildAt(0) 获取到的 mChildOfContent,也就是我们用 setContentView 放进去的 View。
(2). 给我们的 Activity 的xml布局View设置一个 Listener 监听:
View.getViewTreeObserver() 可以获取一个 ViewTreeObserver对象——它是一个观察者,用以监听当前 View树 所发生的变化。这里所注册的 addOnGlobalLayoutListener,就是会在当前的 View树 的全局布局(GlobalLayout)发生变化、或者其中的 View 可视状态有变化时,进行通知回调。『软键盘弹出/隐 』都能监听到。
(3). 获取当前界面可用高度
如下图所示:
(4)重设高度, 我们计算出的可用高度,是目前在视觉效果上能看到的界面高度。但当前界面的实际高度是比可用高度要多出一个软键盘的距离的。
通过上面的这种方法,一般布局输入键盘挡住输入框的问题基本都能解决。即使界面全屏或是沉浸式状态栏情况。
总结
下面对上面几种方法进行对比:
方法一:
-
优点:使用简单,只需在Activity的AndroidMainfest.xml中设置windowSoftInput属性即可。
-
注意点:adjustResize属性必须要界面大小可以自身改变;
-
缺点:当输入框比较多时,当前输入框下方的输入框会初键盘挡住,须收起键盘再进入输入;使用adjustPan,输入框较多时,因它是把界面当成一个整体,只会显示一屏的高度,会把ActionBar顶上去。
方法二:
-
优点:使用简单,只需在Activity的最外层布局包裹一个ScrollView即可。
-
注意点:不可使用adjustPan属性,否则ScrollView失效;
-
缺点:对于全屏时,在键盘显示时,无法上下滑动界面达到输入的目的;
方法三:
-
优点:可以解决全屏时,键盘挡入按钮问题。
-
缺点:只要有此需求的Activity均需要获取到最外层控件和最后一个控件,监测键盘是否弹出,再调用控件的scrollTo方法对界面整体上移或是下移。代码冗余。
方法四:
-
优点:可以解决全屏时,键盘挡入输入框问题。只需要写一个全局类,其他有需求的界面直接在onCreate方法里调用此类的全局方法,即可。
-
缺点:多用了一个类。
综上所述:
1) 当输入框比较少时,界面只有一个输入框时,可以通过方法一设置adjustPan;
2) 如果对于非全屏/非沉浸式状态栏需求,只需要使用方法二即可;
3) 如果全屏全屏/沉浸式状态栏界面只有一个类有键盘挡入输入框需求,可使用方法三;
4) 如果大部分界面均使用全屏或沉浸式状态栏,且有此需求,则选择方法四更恰当。