Android软键盘windowSoftInputMode的使用与原理(使用篇)

前言

看了网上很多文章都没有很准确的说明windowSoftInputMode的几种模式的作用,而且大部分文章只是分析了一下如何使用并没有深入源码去分析原理,今天这篇文章就来分析一下Android软键盘windowSoftInputMode的各个模式的作用和原理,文章一共分为两篇,一是使用篇,二是原理篇,下面开始使用篇的讲解。

一、模式介绍

android:windowSoftInputMode=["stateUnspecified",
                                       "stateUnchanged", "stateHidden",
                                       "stateAlwaysHidden", "stateVisible",
                                       "stateAlwaysVisible", "adjustUnspecified",
                                       "adjustResize", "adjustPan"]

如上,windowSoftInputMode有六个stateXXX的值和三个adjustXXX的值。简单地说,state前缀的值控制软件输入法在Activity成为用户焦点时的可见性,adjust前缀控制其在窗口的显示方式。
两种值可组合使用。
可见性由两种:隐藏和显示
显示方式有两种:弹出软窗时,要么缩小内容部分,要么调整内容的位置,使其焦点控件处理可见窗口内。
每个值的意义,如下表(官方)

说明
"stateUnspecified" 不指定软键盘的状态(隐藏还是可见)。 将由系统选择合适的状态,或依赖主题中的设置。

这是对软键盘行为的默认设置。

stateUnchanged 当 Activity 转至前台时保留软键盘最后所处的任何状态,无论是可见还是隐藏。
stateHidden 当用户选择 Activity 时 — 也就是说,当用户确实是向前导航到 Activity,而不是因离开另一 Activity 而返回时 — 隐藏软键盘。
stateAlwaysHidden 当 Activity 的主窗口有输入焦点时始终隐藏软键盘。
stateVisible 当用户选择 Activity 时 — 也就是说,当用户确实是向前导航到 Activity,而不是因离开另一 Activity 而返回时 — 显示软键盘。
stateAlwaysVisible 当 Activity 的主窗口有输入焦点时始终显示软键盘。
adjustUnspecified 不指定 Activity 的主窗口是否调整尺寸以为软键盘腾出空间,或者窗口内容是否进行平移以在屏幕上显露当前焦点。 系统会根据窗口的内容是否存在任何可滚动其内容的布局视图来自动选择其中一种模式。 如果存在这样的视图,窗口将进行尺寸调整,前提是可通过滚动在较小区域内看到窗口的所有内容。

这是对主窗口行为的默认设置。

adjustResize 始终调整 Activity 主窗口的尺寸来为屏幕上的软键盘腾出空间。
adjustPan 不调整 Activity 主窗口的尺寸来为软键盘腾出空间, 而是自动平移窗口的内容,使当前焦点永远不被键盘遮盖,让用户始终都能看到其输入的内容。 这通常不如尺寸调正可取,因为用户可能需要关闭软键盘以到达被遮盖的窗口部分或与这些部分进行交互。

二、可见性使用

通过上面的解释并不能很清晰的了解每个属性的具体作用,下面我来逐一详细介绍每个属性的作用。
为了方便展示我会创建两个Activity:InputActivity1和InputActivity2,并分别设置他们的属性进行操作。

1.stateUnchanged

当 Activity 转至前台时保留软键盘最后所处的任何状态,无论是可见还是隐藏。
这句话怎么理解呢?我们把两个Activity的windowSoftInputMode都设置为stateUnchanged。然后看下面的一个操作实例:
在这里插入图片描述

说明一下现象,首先进入InputActivity1时是没有显示软键盘的,在InputActivity1上呼出软键盘,然后跳转到InputActivity2,这时会发现InputActivity2上也会展示软键盘。继续看下面的操作实例:
在这里插入图片描述

在InputActivity1上没有呼出软键盘,然后跳转到InputActivity2,这时会发现InputActivity2上也没有展示软键盘。
通过这两个实例我们得出结论stateUnchanged不会改变软键盘的显示状态,如果软键盘之前是显示的,那么到其他Activity时也会显示,反之亦是如此。

刚才的实例都是前进到另一个Activity,如果是回退到上一个Activity呢?
在这里插入图片描述

首先InputActivity1没有显示软键盘的情况下跳转到InputActivity2,在InputActivity2手动唤起软键盘,之后回退到InputActivity1,可以发现这时软键盘也是显示的,由此可见无论是前进还是后退stateUnchanged都适用。

2.stateHidden

当用户选择 Activity 时 — 也就是说,当用户确实是向前导航到 Activity,而不是因离开另一 Activity 而返回时 — 隐藏软键盘。
同样的我们把两个Activity的windowSoftInputMode都设置为stateHidden。然后看下面的一个操作实例:
在这里插入图片描述
首先进入InputActivity1是没有显示软键盘的,手动唤起软键盘后跳转到InputActivity2,在InputActivity2上软键盘被隐藏了,这说明stateHidden起作用了,当用户向前导航到 Activity时软键盘会被隐藏
之后在InputActivity2上唤起软键盘并回退到InputActivity1,可以发现这时InputActivity1软键盘是显示的,由此可见只有前进的情况下stateUnchanged才生效。

3.stateAlwaysHidden

当 Activity 的主窗口有输入焦点时始终隐藏软键盘。
虽然同样是隐藏软键盘,但是stateAlwaysHidden的限定词少了,前面说到stateHidden必须是向前导航到Activity才生效,而stateAlwaysHidden就没有了这个限制,无论是向前还是向后都生效。我们来看一下实际操作,同样我们把两个Activity的windowSoftInputMode都设置为stateAlwaysHidden:
在这里插入图片描述
和上面的stateHidden同样的操作,但是在InputActivity2上唤起软键盘并回退到InputActivity1的时候,可以发现这时InputActivity1软键盘被隐藏了,由此可见无论是前进还是后退stateAlwaysHidden都适用。

4.stateVisible

当用户选择 Activity 时 — 也就是说,当用户确实是向前导航到 Activity,而不是因离开另一 Activity 而返回时 — 显示软键盘。
Visible和Hidden的意思一样,为了更清晰的看出他的作用,我们这次把InputActivity1设置为stateVisible,InputActivity2设置为stateHidden,然后看下面的一个操作实例:
在这里插入图片描述
刚进入到InputActivity1的时候软键盘就自动显示了,进入到InputActivity2的时候由于设置的stateHidden又隐藏了,再次返回InputActivity1的时候也没有显示出来,此可见只有前进的情况下stateVisible才生效。

关于Visible的两个属性有个需要注意的点:
在Android9.0以上的版本也就是API28,关于显示的两个属性都会失效,这一点在源码的注释中就说明了:

        /**
         * Visibility state for {@link #softInputMode}: please show the soft
         * input area when normally appropriate (when the user is navigating
         * forward to your window).
         *
         * <p>Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag
         * is ignored unless there is a focused view that returns {@code true} from
         * {@link View#isInEditMode()} when the window is focused.</p>
         */
        public static final int SOFT_INPUT_STATE_VISIBLE = 4;

        /**
         * Visibility state for {@link #softInputMode}: please always make the
         * soft input area visible when this window receives input focus.
         *
         * <p>Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag
         * is ignored unless there is a focused view that returns {@code true} from
         * {@link View#isInEditMode()} when the window is focused.</p>
         */
        public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;

Applications that target {@link android.os.Build.VERSION_CODES#P} and later, this flag is ignored unless there is a focused view that returns {@code true} from {@link View#isInEditMode()} when the window is focused.

这篇文章详细介绍了失效的原因:https://blog.csdn.net/weimingjue/article/details/99738271
简单来说就是Android9.0之前一个Activity启动之后会自动获取焦点,而之后不会了,也就导致软键盘的显示失效了,解决方案是在Activity启动之后对需要输入的控件手动获取焦点view.requestFocus()

5.stateAlwaysVisible

当 Activity 的主窗口有输入焦点时始终显示软键盘。
这个就不多说了,直接看操作实例 :
在这里插入图片描述
相比与stateVisible,返回到InputActivity1的时候软键盘自动显示了。由此可见无论是前进还是后退stateAlwaysVisible都适用。

6.stateUnspecified

不指定软键盘的状态(隐藏还是可见)。 将由系统选择合适的状态,或依赖主题中的设置。
这是对软键盘行为的默认设置。

这种不指定具体方式的设置我就不详细说了(还挺复杂的),只要能掌握上面其他的几种设置应该就能满足日常的开发需要。

三、显示方式

先简单分析:
我们知道软键盘其实就是个Dialog,当键盘弹起的时,实际上当前能看到的是两个Window:1是Activity的Window,2是Dialog的Window。问题的关键是Dialog的Window展示遮盖了Activity的Window的部分区域,为了使EditText能够被看到,Activity的布局被向上顶上去,猜想Window 属性有控制是否顶上去的参数。还真有,这个参数就是WindowManager.LayoutParams.softInputMode。

1.adjustResize

始终调整 Activity 主窗口的尺寸来为屏幕上的软键盘腾出空间。
adjustResize实际上就是给根布局加Padding,padding的值就是软键盘的高度。 你可以直接给根布局加padding,效果是一样的。

我们使用如下布局代码进行测试:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/myviewgroup"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/iv"
        android:src="@drawable/test"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="20dp"
        android:layout_width="match_parent"
        android:layout_height="300dp">
    </ImageView>

    <EditText
        android:hint="输入框2"
        android:id="@+id/et2"
        android:layout_gravity="bottom"
        android:layout_marginTop="200dp"
        android:layout_width="100dp"
        android:layout_height="40dp">
    </EditText>

</LinearLayout>

将Activity设置为adjustResize,运行后效果如下:
在这里插入图片描述
可以看到布局并没有任何变化,输入框被隐藏,这是因为布局中的控件长度都是固定的,即使加了padding也没有任何效果。
接下来我们修改一下布局,将Imageview的高度设为layout_weight=1占满剩余屏幕。
修改后的内容如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/myviewgroup"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/iv"
        android:src="@drawable/test"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="20dp"
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:scaleType="fitXY"
        android:layout_height="0dp">
    </ImageView>

    <EditText
        android:hint="输入框2"
        android:id="@+id/et2"
        android:layout_width="100dp"
        android:layout_height="40dp">
    </EditText>

</LinearLayout>

在这里插入图片描述
可以看到不是固定长度的布局内容被顶上去并且高度被压缩了。

2.adjustPan

不调整 Activity 主窗口的尺寸来为软键盘腾出空间, 而是自动平移窗口的内容,使当前焦点永远不被键盘遮盖,让用户始终都能看到其输入的内容。 这通常不如尺寸调正可取,因为用户可能需要关闭软键盘以到达被遮盖的窗口部分或与这些部分进行交互。
adjustPan实际上就是布局整体向上滚动,这里无论什么样的布局都是一个效果,但是需要注意的是向上滚动的布局是根据当前焦点所在的位置确定的

使用和adjustResize一样的布局进行测试

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/myviewgroup"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:background="@color/white"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:scaleType="fitXY"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="20dp"
        android:src="@drawable/test"></ImageView>

    <EditText
        android:id="@+id/et2"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:layout_marginTop="100dp"
        android:hint="输入框2"></EditText>
</LinearLayout>

在这里插入图片描述

3.adjustUnspecified

不指定 Activity 的主窗口是否调整尺寸以为软键盘腾出空间,或者窗口内容是否进行平移以在屏幕上显露当前焦点。 系统会根据窗口的内容是否存在任何可滚动其内容的布局视图来自动选择其中一种模式。 如果存在这样的视图,窗口将进行尺寸调整,前提是可通过滚动在较小区域内看到窗口的所有内容。这是对主窗口行为的默认设置。

adjustUnspecified实际上就是从adjustResize和adjustPan中选择一个,如果布局内容中有可滚动布局就选择adjustResize,如果没有就选择adjustPan,那么什么样的内容是可滚动内容呢?其实主要是isScrollContainer这个属性定义的,当布局的属性isScrollContainer设置为true的时候就代表这个布局是可以滚动的。

我们平时用的RecyclerView、ScrollView都是属于可滚动的内容,他们在初始化的时候就设置了这个属性

    public RecyclerView(Context context, @android.annotation.Nullable AttributeSet attrs, int defStyle) {
    
    
        ...
        setScrollContainer(true);
        ...
    }

对于普通的View,如果想要设置该属性有两种方法:

  1. 代码里:View.setScrollContainer(true)
  2. xml里: android:isScrollContainer=“true”

还用上面的布局进行测试

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/myviewgroup"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:background="@color/white"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:scaleType="fitXY"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="20dp"
        android:src="@drawable/test"></ImageView>

    <EditText
        android:id="@+id/et2"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:layout_marginTop="100dp"
        android:hint="输入框2"></EditText>
</LinearLayout>

在这里插入图片描述
可以看到由于没有可滑动布局,这里选择了adjustPan将整个布局顶了上去,下面我们在ImageView上加上isScrollContainer属性:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/myviewgroup"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:background="@color/white"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:scaleType="fitXY"
        android:isScrollContainer="true"
        android:layout_marginTop="20dp"
        android:layout_marginBottom="20dp"
        android:src="@drawable/test"></ImageView>

    <EditText
        android:id="@+id/et2"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:layout_marginTop="100dp"
        android:hint="输入框2"></EditText>
</LinearLayout>

在这里插入图片描述
这里就选择了adjustResize。

4.adjustNothing

不进行任何操作。

总结

以上是关于软键盘弹出、关闭、是否展示、软键盘高度、软键盘模式等效果的解析。
如果想了解以上效果的实现原理可以看下一篇文章Android软键盘windowSoftInputMode的使用与原理(原理篇)

猜你喜欢

转载自blog.csdn.net/shanshui911587154/article/details/124462145