Android CoordinatorLayout和Behavior解析

o0daxu0o 2019-04-26

在开发中,经常会遇到键盘挡住输入框的情况,比如登录界面或注册界面,弹出的软键盘把登录或注册按钮挡住了,用户必须把软键盘收起,才能点击相应按钮,这样的用户体验非常不好。像微信则直接把登录按钮做在输入框的上面,但有很多情况下,这经常满足不了需求。同时如果输入框特别多的情况下,点击输入时,当前输入框没被挡住,但是当前输入框下面的输入框却无法获取焦点,必须先把键盘收起,再去获取下面输入框焦点,这样用户体验也非常不好,那有什么办法呢?

系统的 adjustResize 和 adjustPan 有什么区别,他们使用时的注意事项,有什么系统要求及蔽端呢?

下面对几种在开发中常用的方法进行总结:

方法一:windowSoftInputMode:adjustResize和adjustPan

主要实现方法:在 AndroidManifest.xml 对应的Activity里添加 android:windowSoftInputMode=”adjustPan” 或是 android:windowSoftInputMode=”adjustResize”属性

这两种属性的区别,官方的解释是:

Android CoordinatorLayout和Behavior解析

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

Android CoordinatorLayout和Behavior解析

  • adjustResize失效情况:activity 设置了全屏属性指 Theme.Light.NotittleBar.Fullscreen 或者设置了 activity 对应的主题中 android:windowTranslucentStatus 属性,设置方式为:android:windowTranslucentStatus=true,这时如果对应的页面上含有输入框,将会导致点击输入框时软键盘弹出后键盘覆盖输入框,导致输入框看不见。
  • fitsSystemWindows=”true”,只有初始的view起作用:如果在布局中不是最外层控件设置 fitsSystemWindows=”true”, 那么设置的那个控件高度会多出一个状态栏高度。若有多个view设置了,因第一个view已经消耗掉 insect,其他 view 设置了也会被系统忽略。

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

Android CoordinatorLayout和Behavior解析

1、adjustPan

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

Android CoordinatorLayout和Behavior解析

2、adjustResize

需要界面的高度是可变的,或者说 Activity 主窗口的尺寸是可以调整的,如果不能调整,则不会起作用。

例如:Activity 的xml布局中只有一个 LinearLayout 包含若干EditText,在Activity的 AndroidMainfest.xml 中设置 android:windowSoftInputMode=”adjustResize” 属性,点击 输入框6, 发现软键盘挡住了 输入框6,并没有调整,如下图所示:

Android CoordinatorLayout和Behavior解析

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

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

2). adjustResize 的使用,需要界面本身可显示的窗口内容能调整,如果不能,不起作用;

方法二:在界面最外层布局包裹ScrollView

1、只使用ScrollView

在相应界面的xml布局中,最外层添加一个 ScrollView,不在 AndroidMainfest.xml 中设置任何 android:windowSoftInputMode属性,此时点击输入框,输入框均不会被软键盘档住。即使当前输入框下方也有输入框,在键盘显示的情况下,也可以通过上下滑动界面来输入,而不用先隐藏键盘,点击下方输入框,再显示键盘输入。

我们可以根据 Android Studio 的 Inspect Layout 工具来查看界面真正占用的布局高度,工具在:

Android CoordinatorLayout和Behavior解析

通过该工具,我们看到:

界面真正能用的高度=屏幕高度-状态栏高度-软键盘高度

界面中蓝框是真正界面所用的高度:

Android CoordinatorLayout和Behavior解析

2、ScrollView + adjustPan

我们再在该类的 AndroidMainfest.xml 中设置 windowSoftInputMode属性 为 adjustPan:

Android CoordinatorLayout和Behavior解析

发现当前输入框不会被挡住,但是输入框比较多时,在有键盘显示时,界面上下滑动,但只能滑动部分,且如果输入框在界面靠下方时,点击输入框,标题栏也会被顶出去,如下图所示:

Android CoordinatorLayout和Behavior解析

我们借助 Inspect Layout 工具查看此设置布局可用高度,从下图可以看出,此时布局可用高度是屏幕的高度,上下滑动也只是此屏的高度,在 输入框9 以下的输入框滑不出来,向上滑动,也只能滑到 输入框1。

Android CoordinatorLayout和Behavior解析

3、ScrollView+adjustResize

我们前面说过 adjustResize 的使用必须界面布局高度是可变的,如最外层套个 ScrollView 或是界面可收缩的,才起作用。这里在该类的 AndroidMainfest.xml 中设置windowSoftInputMode属性 为 adjustResize

Android CoordinatorLayout和Behavior解析

发现效果和 1 不设置任何 windowSoftInputMode属性 类似,其使用高度也是:屏幕高度-状态栏高度-软键盘高度

Android CoordinatorLayout和Behavior解析

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

Android CoordinatorLayout和Behavior解析

可以看出,系统将选择合适的状态,也就是在界面最外层包含一层 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文件如下:

Android CoordinatorLayout和Behavior解析

先获取到最外层控件:

RelativeLayout main = (RelativeLayout) findViewById(R.id.main);

(2). 获取到最后一个控件,如上面的xml文件,最后一个控件是Button:

Button login_btn = (Button) findViewById(R.id.login_btn);

(3). 给最外层控件和最后一个控件添加监听事件:

Android CoordinatorLayout和Behavior解析

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类 具体代码如下:

Android CoordinatorLayout和Behavior解析

它的实现原理主要是:

(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 监听:

Android CoordinatorLayout和Behavior解析

View.getViewTreeObserver() 可以获取一个 ViewTreeObserver对象——它是一个观察者,用以监听当前 View树 所发生的变化。这里所注册的 addOnGlobalLayoutListener,就是会在当前的 View树 的全局布局(GlobalLayout)发生变化、或者其中的 View 可视状态有变化时,进行通知回调。『软键盘弹出/隐 』都能监听到。

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

Android CoordinatorLayout和Behavior解析

如下图所示:

Android CoordinatorLayout和Behavior解析

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

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

总结

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

方法一:

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

方法二:

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

方法三:

  • 优点:可以解决全屏时,键盘挡入按钮问题。
  • 缺点:只要有此需求的Activity均需要获取到最外层控件和最后一个控件,监测键盘是否弹出,再调用控件的scrollTo方法对界面整体上移或是下移。代码冗余。

方法四:

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

综上所述:

1) 当输入框比较少时,界面只有一个输入框时,可以通过方法一设置adjustPan;

2) 如果对于非全屏/非沉浸式状态栏需求,只需要使用方法二即可;

3) 如果全屏全屏/沉浸式状态栏界面只有一个类有键盘挡入输入框需求,可使用方法三;

4) 如果大部分界面均使用全屏或沉浸式状态栏,且有此需求,则选择方法四更恰当。

相关推荐