Android 提供了 windowSoftInputMode 属性来控制输入法软键盘窗口和 Activity 主窗口的交互,分为 窗口尺寸调整系列 和 输入法软键盘显示控制系列。
窗口尺寸调整系列:
该系列参数用来控制当软键盘弹起时,Activity 主窗口的调整策略,因为如果不调整主窗口,很可能会导致当前输入的控件被软键盘遮挡。
adjustPan:
Activity的主窗口并不会重新调整大小来为输入法腾出空间,而是窗口的内容会自动上下晃动来保证当前获得焦点的控件不会被键盘遮挡住,然后用户可以看见自己输入的内容。相比于 adjustResize 模式而言,它并不是很令人满意,因为用户必须关闭输入法来和被输入法遮挡住的控件进行交互。
adjustResize:
Activity的主窗口会重新调整大小来为输入法腾出空间。
adjustUnspecified:
当前模式并不会明确指定 Activity 使用adjustPan 或者 adjustResize ,系统会自动选择一个模式,选择结果是,如果当前 Activity 的 Window 中,有可以滚动自身内容的控件,比如 ScrollView,那么选择结果就是 adjustResize,因为它认为滚动可以使 Window 中的内容即使在一个很小的区域中也可以被看见。Activity 的默认模式就是这个模式。
adjustNoting:
Activity 的 Window 没有任何变化。
两种模式下,屏幕 - 主窗口 - 主窗口内容 - 软键盘 的关系如下:
具体效果如下:
没有滚动内容,adjustPan:
没有滚动内容,adjustResize:
没有滚动内容,adjustUnspecified == adjustPan
有滚动内容(ScrollView),adjustPan
有滚动内容(ScrollView),adjustResize
有滚动内容(ScrollView),adjustUnspecified == adjustResize
输入法软键盘显示控制系列:
该系列参数用来控制当一个包含 Window 的事物(Activity、Dialog等)展示在屏幕最前端时,软键盘的显示或者隐藏策略。
stateUnspecified:
系统根据当前具体情况,选择相应的模式。
stateUnchanged:
软键盘保持它的上一个状态(上一个Activity 或者 Dialog 在屏幕最前端时,软键盘的状态),不做变化,不管上一个状态是显示还是隐藏。
stateHidden:
当用户主动进入当前界面时,软键盘隐藏。离开上一个 界面,返回当前界面,不能算作 “主动进入”。被动进入时,保持上一个状态。
stateAlwaysHidden:
只要是用户进入该界面,就隐藏软键盘,不管是主动进入(新启动该界面),还是被动进入(离开上一个界面,返回到当前界面)。
stateVisible:
当用户主动进入当前界面时,显示软键盘。离开上一个 界面,返回当前界面,不能算作 “主动进入”。被动进入时,保持上一个状态。
stateAlwaysVisible:
只要是用户进入该界面,就显示软键盘,不管是主动进入(新启动该界面),还是被动进入(离开上一个界面,返回到当前界面)。
关于某些华为手机的一个Bug
今天遇到一个Bug,是这样的,在某些华为手机上面,除了第一次点击输入框,adjustPan 参数会生效(软键盘可以正常弹起输入框),后面从第二次开始,怎么点击,adjustPan 参数都无效。
布局模型大致如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="cn.hjf.inputtest.MainActivity"> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <View android:layout_width="match_parent" android:layout_height="400dp" android:background="#2b532b"/> <EditText android:layout_width="90dp" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:background="@null" android:gravity="center" android:inputType="numberDecimal" android:maxLength="8" android:minWidth="60dp" android:padding="5dp" android:text="0.00"/> </LinearLayout> </ScrollView> </RelativeLayout>
效果是这样的:
经过漫长时间的排除(很辛苦),终于找到了Bug的触发临界点,就是下面这两句话:
android:gravity="center" android:inputType="numberDecimal"
又经过了漫长时间的验证,得出一个结论:在某些华为机型上面,在这种布局模型下,EditText 中如果设置了 inputType 参数(不为 none),那么,在 gravity 取值为某些值得时候,会导致该问题的发生。大致情况如下:
红色表示失效,绿色表示有效(可以正常工作)。当这两个参数不指定的时候,可以工作,因为这两个属性默认值的组合是可以工作的,上图中的蓝色块。(备注:没有完全匹配所有情况,如有相似情况,可以按需匹配,查看结果)
在源码中可以找到这两个属性的默认值:
attrs.xml
<!-- Default EditText style. --> <attr name="editTextStyle" format="reference" />
themes.xml
<item name="editTextStyle">@style/Widget.EditText</item>
styles.xml
<style name="Widget.EditText"> <item name="focusable">true</item> <item name="focusableInTouchMode">true</item> <item name="clickable">true</item> <item name="background">?attr/editTextBackground</item> <item name="textAppearance">?attr/textAppearanceMediumInverse</item> <item name="textColor">?attr/editTextColor</item> <item name="gravity">center_vertical</item> <item name="breakStrategy">simple</item> <item name="hyphenationFrequency">normal</item> </style>
attrs.xml
<!-- The type of data being placed in a text field, used to help an input method decide how to let the user enter text. The constants here correspond to those defined by {@link android.text.InputType}. Generally you can select a single value, though some can be combined together as indicated. Setting this attribute to anything besides <var>none</var> also implies that the text is editable. --> <attr name="inputType"> <!-- There is no content type. The text is not editable. --> <flag name="none" value="0x00000000" /> ..........
/** * Special content type for when no explicit type has been specified. * This should be interpreted to mean that the target input connection * is not rich, it can not process and show things like candidate text nor * retrieve the current text, so the input method will need to run in a * limited "generate key events" mode, if it supports it. Note that some * input methods may not support it, for example a voice-based input * method will likely not be able to generate key events even if this * flag is set. */ public static final int TYPE_NULL = 0x00000000;
一个需求
遇到一个需求,大致模型为:有一个界面,里面有一个 输入框 和 两个按钮,分别控制输入框中数字的加减,如下图所示:
每次输入框数字变化,不管是手动输入还是按钮控制,都会出发刷新工作,刷新时会弹出一个对话框。
然后需求是这样的,当手动输入的时候,这时触发刷新时,输入法是显示的,但是对话框显示然后消失后,输入法就被隐藏了。当用按钮控制的时候,是没问题的,因为整个过程,输入法都是隐藏的。所以需求就是,在对话框显示然后消失后,输入法能保持出发刷新时的状态。
解决问题:当把当前 Activity 设置为 stateUnchanged 的时候,并没有达到理想的效果,因为dialog所在的window,没有设置该属性,所以需要把dialog的window也设置为stateUnchanged,方法为:
getWindow().setSoftInputMode()
然后就可以了。