Android软键盘挡住输入框的解决方案

96
tome869535144
1.9 2018.05.30 15:09* 字数 2992

今天测试提了一个bug,说软键盘遮挡了editText的输入框,但是我测试了都不会呀,原来其他机型不会,在华为的某款手机上会出现,哎,这是个坑呀.于是百度了一番,随便把这个知识点复习一遍,现在把网上关于该问题的处理方案总结一下.

效果图

3.png

方法一:非透明状态栏下使用adjustResize和adjustPan,或是透明状态栏下使用fitsSystemWindows=true属性

主要实现方法:
AndroidManifest.xml对应的Activity里添加
android:windowSoftInputMode=”adjustPan”或是android:windowSoftInputMode=”adjustResize”属性
这两种属性的区别,官方的解释是:

image.png

这两个属性作用都是为了调整界面使键盘不挡住输入框 ,我这里对这两种属性使用场景、优缺点、注意事项进行了全方面总结,不知大家平时使用时是否注意到了。

1.png
  • adjustResize失效情况:activity设置了全屏属性指Theme.Light.NotittleBar.Fullscreen(键盘弹起时会将标题栏也推上去)或者设置了activity对应的主题中android:windowTranslucentStatus属性,设置方式为:android:windowTranslucentStatus=true,这时如果对应的页面上含有输入框,将会导致点击输入框时软键盘弹出后键盘覆盖输入框,导致输入框看不见。

  • fitsSystemWindows=”true”只有初始的view起作用:如果在布局中不是最外层控件设置fitsSystemWindows=”true”那么设置的那个控件高度会多出一个状态栏高度。若有多个view设置了,因第一个view已经消耗掉insect,其他view设置了也会被系统忽略。

假设原始界面是一个LinearLayout包含若干EditText,如下图所示,在分别使用两种属性时的表现。

1、adjustPan

整个界面向上平移,使输入框露出,它不会改变界面的布局;界面整体可用高度还是屏幕高度,这个可以通过下面的截图看出,如点击输入框6,输入框会被推到键盘上方,但输入框1被顶出去了,如果界面包含标题栏,也会被顶出去。

2、adjustResize

需要界面的高度是可变的,或者说Activity主窗口的尺寸是可以调整的,如果不能调整,则不会起作用。
例如:Activity的xml布局中只有一个LinearLayout包含若干EditText,在Activity的AndroidMainfest.xml中设置android:windowSoftInputMode=”adjustResize”属性,点击输入框6, 发现软键盘挡住了输入框6,并没有调整.

但使用这两种属性,我们可以总结以下几点:

  1. 使用adjustPan, 如果需要输入的项比较多时,点击输入框,当前输入项会被顶到软键盘上方,但若当前输入框下面还有输入项时,却需要先收起键盘,再点击相应的输入项才能输入。这样操作太繁琐了,对于用户体验不大好;

  2. adjustResize的使用,需要界面本身可显示的窗口内容能调整,可结合scrollview使用;

方法二:在界面最外层布局包裹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属性类似,其使用高度也是:屏幕高度-状态栏高度-软键盘高度

我们再来看看windowSoftInputMode默认属性值stateUnspecified:

可以看出,系统将选择合适的状态,也就是在界面最外层包含一层ScrollView时,设置默认属性值stateUnspecified其实就是adjustResize属性。

但以下两方面无法满足需求:

  1. 当Activity设置成全屏fullscreen模式时或是使用沉浸式状态栏时,界面最外层包裹 ScrollView,当输入框超过一屏,当前输入框下面的输入框并不能上下滑动来输入,情况类似于ScrollView+adjustPan,只能滑动部分,通过Inspect Layout也可以看到,界面可用高度是整个屏幕高度,并不会进行调整高度。即使设置adjustResize,也不起作用。
  2. 如果是类似于注册界面或是登录界面,键盘会挡住输入框下面的登录按钮。

沉浸式状态栏/透明状态栏情况下

自android系统4.4(API>=19)就开始支持沉浸式状态栏,当使用觉System windows(系统窗口),显示系统一些属性和操作区域,如 最上方的状态及没有实体按键的最下方的虚拟导航栏。
android:fitsSystemWindows=“true”会使得屏幕上的可布局空间位于状态栏下方与导航栏上方

方法三:监听Activity顶层View,判断软键盘是否弹起,对界面重新绘制

此方法的实现来自android中提出的issue 5497 https://code.google.com/p/android/issues/detail?id=5497

使用场景:针对界面全屏或是沉浸式状态栏,界面包含比较多输入框,界面即使包裹了一层ScrollView,在键盘显示时,当前输入框下面的输入不能通过上下滑动界面来输入。

感谢下面提出评论的同学,指出此方法的不适配问题,之前写的博文在华为小米手机上确实有不适配情况,在输入时,键盘有时会错乱,现在已加入适配。

一、实现步骤:

1、把SoftHideKeyBoardUtil类复制到项目中;
2、在需要使用的ActivityonCreate方法中添加:SoftHideKeyBoardUtil.assistActivity(this);即可。

二、实现原理:

SoftHideKeyBoardUtil类具体代码如下:

/**
 * 解决键盘档住输入框
 * Created by SmileXie on 2017/4/3.
 */public class SoftHideKeyBoardUtil {
 public static void assistActivity (Activity activity) { new SoftHideKeyBoardUtil(activity);
 } private View mChildOfContent; private int usableHeightPrevious; private FrameLayout.LayoutParams frameLayoutParams; //为适应华为小米等手机键盘上方出现黑条或不适配
 private int contentHeight;//获取setContentView本来view的高度
 private boolean isfirst = true;//只用获取一次
 private int statusBarHeight;//状态栏高度
 private SoftHideKeyBoardUtil(Activity activity) { //1、找到Activity的最外层布局控件,它其实是一个DecorView,它所用的控件就是FrameLayout
 FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content); //2、获取到setContentView放进去的View
 mChildOfContent = content.getChildAt(0); //3、给Activity的xml布局设置View树监听,当布局有变化,如键盘弹出或收起时,都会回调此监听 
  mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { //4、软键盘弹起会使GlobalLayout发生变化
  public void onGlobalLayout() {  if (isfirst) {
   contentHeight = mChildOfContent.getHeight();//兼容华为等机型
   isfirst = false;
  }  //5、当前布局发生变化时,对Activity的xml布局进行重绘
  possiblyResizeChildOfContent();
  }
 }); //6、获取到Activity的xml布局的放置参数
 frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
 } // 获取界面可用高度,如果软键盘弹起后,Activity的xml布局可用高度需要减去键盘高度 
 private void possiblyResizeChildOfContent() { //1、获取当前界面可用高度,键盘弹起后,当前界面可用布局会减少键盘的高度
 int usableHeightNow = computeUsableHeight(); //2、如果当前可用高度和原始值不一样
 if (usableHeightNow != usableHeightPrevious) {  //3、获取Activity中xml中布局在当前界面显示的高度
  int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();  //4、Activity中xml布局的高度-当前可用高度
  int heightDifference = usableHeightSansKeyboard - usableHeightNow;  //5、高度差大于屏幕1/4时,说明键盘弹出
  if (heightDifference > (usableHeightSansKeyboard/4)) {  // 6、键盘弹出了,Activity的xml布局高度应当减去键盘高度
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
   frameLayoutParams.height = usableHeightSansKeyboard - heightDifference + statusBarHeight;
  } else {
   frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
  }
  } else {
  frameLayoutParams.height = contentHeight;
  }  //7、 重绘Activity的xml布局
  mChildOfContent.requestLayout();
  usableHeightPrevious = usableHeightNow;
 }
 } private int computeUsableHeight() {
 Rect r = new Rect();
 mChildOfContent.getWindowVisibleDisplayFrame(r); // 全屏模式下:直接返回r.bottom,r.top其实是状态栏的高度
 return (r.bottom - r.top);
 }
}

它的实现原理主要是:
(1) 找到Activity的最外层布局控件,我们知道所有的Activity都是DecorView,它就是一个FrameLayout控件,该控件id是系统写死叫R.id.content,就是我们setContentView时,把相应的View放在此FrameLayout控件里
所以content.getChildAt(0)获取到的mChildOfContent,也就是我们用setContentView放进去的View。
(2) 给我们的Activity的xml布局View设置一个Listener监听

mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener({ 
 possiblyResizeChildOfContent();
});

View.getViewTreeObserver()可以获取一个ViewTreeObserver对象——这个对象是一个观察者,专门用以监听当前View树所发生的一些变化。这里所注册的addOnGlobalLayoutListener,就是会在当前的View树的全局布局(GlobalLayout)发生变化、或者其中的View可视状态有变化时,进行通知回调。

——『软键盘弹出』,则是会触发这个事件的一个源。 (软键盘弹出会使GlobalLayout发生变化)

也就是说,现在能监听到『软键盘弹出』的事件了。

上面的代码里添加了一个”heightDifference > (usableHeightSansKeyboard/4)”的判断,这是为了去除无谓的干扰。因为能触发OnGlobalLayout事件的原因有很多,不止是软键盘的弹出变化,还包括各种子View的隐藏显示变化等,它们对界面高度的影响有限。加上了这个判断之后,只有界面的高度变化超过1/4的屏幕高度,才会进行重新设置高度,基本能保证代码只响应软键盘的弹出

(3) 获取当前界面可用高度

private int computeUsableHeight() {
 Rect rect = new Rect();
 mChildOfContent.getWindowVisibleDisplayFrame(rect); // rect.top其实是状态栏的高度,如果是全屏主题,直接 return rect.bottom就可以了
 return (rect.bottom - rect.top);
}

如下图所示:

1.jpg

(4) 重设高度, 我们计算出的可用高度,是目前在视觉效果上能看到的界面高度。但当前界面的实际高度是比可用高度要多出一个软键盘的距离的。

注意:如果既使用了沉浸式状态栏,又加了fitSystetemWindow=true属性,就需要在AndroidMainfest.xml注册Activity的地方添加上以下属性。因为你两种都用,系统不知道用哪种了。fitSystetemWindow已经有resize屏幕的作用。

android:windowSoftInputMode="stateHidden|adjustPan"

或者使用

android:windowSoftInputMode="adjustResize"

通过上面的这种方法,一般布局输入键盘挡住输入框的问题基本都能解决。即使界面全屏或是沉浸式状态栏情况。

总结:

下面对上面几种方法进行对比:

  • 方法一:优点:使用简单,只需在Activity的AndroidMainfest.xml中设置windowSoftInput属性即可。
    注意点:adjustResize属性必须要界面大小可以自身改变;
    缺点:当输入框比较多时,当前输入框下方的输入框会初键盘挡住,须收起键盘再进入输入;使用adjustPan,输入框较多时,因它是把界面当成一个整体,只会显示一屏的高度,会把ActionBar顶上去。

  • 方法二:优点:使用简单,只需在Activity的最外层布局包裹一个ScrollView即可。
    注意点:不可使用adjustPan属性,否则ScrollView失效;
    缺点:对于全屏时,在键盘显示时,无法上下滑动界面达到输入的目的;

  • 方法三:优点:可以解决全屏时,键盘挡入输入框问题。只需要写一个全局类,其他有需求的界面直接在onCreate方法里调用此类的全局方法,即可。
    缺点:多用了一个类。

综上所述:

  1. 当输入框比较少时,界面只有一个输入框时,可以通过方法一设置adjustPan;
  2. 如果对于非全屏/非沉浸式状态栏需求,只需要使用方法二ScrollView+adjustResize;
  3. 如果对于使用沉浸式状态栏,使用fitSystemWindow=true属性,按道理android系统已经做好适配,键盘不会挡住输入框;
  4. 如果全屏/沉浸式状态栏界面,类似于登录界面,有需要把登录键钮或是评论按钮也顶起,如果键盘没有变化需求,可以使用方法三,若需要适配键盘高度变化,则需要使用方法四;
  5. 如果界面使用全屏或沉浸式状态栏,没有使用fitSystemWindow=true属性,一般如需要用到抽屈而且状态栏颜色也需要跟着变化,则选择方法五更恰当。

文章来自:http://www.jb51.net/article/137229.htm
参考: http://www.jb51.net/article/95955.htm

日记本