android 中关于键盘和焦点的问题,有时候处理不好,真的让人抓狂,昨天在实现需求的时候被键盘和焦点的问题搞得难受,今天把昨天遇到的问题总结记录一下。
1. 键盘隐藏和遮挡界面
1.1 键盘隐藏
在一个常见的输入信息的界面(保存联系人信息)为例,界面如图所示。
有时候我们需要在进入界面的时候隐藏输入法键盘,有两种方法:
- 在AndroidManifest.xml中对相应的Activity添加一下代码:
android:windowSoftInputMode="stateHidden"
- 在java代码中调用:
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
多说一句:
网上较多的实现方式是通过InputMethodManager
实现:
Timer timer=new Timer();
timer.schedule(new TimerTask() {
public void run() {
InputMethodManager inputMethodManager=(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
}
}, 2000);
但是在我的测试中,并没有实现我的需求,这个代码会在进入界面时弹出键盘,然后在隐藏键盘。而我的需求是,进入界面时就不能显示键盘,所以不能通过InputMethodManager
实现,只能通过windowSoftInputMode
实现。
1.2 键盘遮挡
键盘隐藏已经解决了,再看看,又出现一个问题。如果界面中内容较多,比如上图中备注1的输入框,点击弹起键盘时,默认情况下,键盘会把界面中的内容整体平移推到上面。如图,连标题栏也被挤到屏幕外面去了。
以下内容摘自 彻底搞定Android开发中软键盘的常见问题
Android定义了一个属性,名字为windowSoftInputMode, 这个属性用于设置Activity主窗口与软键盘的交互模式,用于避免软键盘遮挡内容的问题。
我们可以在AndroidManifet.xml中对Activity进行设置。如:android:windowSoftInputMode=”stateUnchanged|adjustPan”。
该属性可选的值有两部分,一部分为软键盘的状态控制,控制软键盘是隐藏还是显示,另一部分是Activity窗口的调整,以便腾出空间展示软键盘。
android:windowSoftInputMode的属性设置必须是下面中的一个值,或一个”state”值加一个”adjust”值的组合,各个值之间用 | 分开。
- stateUnspecified-未指定状态:当我们没有设置android:windowSoftInputMode属性的时候,软件默认采用的就是这种交互方式,系统会根据界面采取相应的软键盘的显示模式。
- stateUnchanged-不改变状态:当前界面的软键盘状态,取决于上一个界面的软键盘状态,无论是隐藏还是显示。
- stateHidden-隐藏状态:当设置该状态时,软键盘总是被隐藏,不管是否有输入的需求。
- stateAlwaysHidden-总是隐藏状态:当设置该状态时,软键盘总是被隐藏,和stateHidden不同的是,当我们跳转到下个界面,如果下个页面的软键盘是显示的,而我们再次回来的时候,软键盘就会隐藏起来。
- stateVisible-可见状态:当设置为这个状态时,软键盘总是可见的,即使在界面上没有输入框的情况下也可以强制弹出来出来。
- stateAlwaysVisible-总是显示状态:当设置为这个状态时,软键盘总是可见的,和stateVisible不同的是,当我们跳转到下个界面,如果下个页面软键盘是隐藏的,而我们再次回来的时候,软键盘就会显示出来。
- adjustUnspecified-未指定模式:设置软键盘与软件的显示内容之间的显示关系。当你跟我们没有设置这个值的时候,这个选项也是默认的设置模式。在这中情况下,系统会根据界面选择不同的模式。
- adjustResize-调整模式:该模式下窗口总是调整屏幕的大小用以保证软键盘的显示空间;这个选项不能和adjustPan同时使用,如果这两个属性都没有被设置,系统会根据窗口中的布局自动选择其中一个。
- adjustPan-默认模式:该模式下通过不会调整来保证软键盘的空间,而是采取了另外一种策略,系统会通过布局的移动,来保证用户要进行输入的输入框肯定在用户的视野范围里面,从而让用户可以看到自己输入的内容。
因为,在默认情况下,系统使用的adjustPan
,这种方式平移界面中的内容,与之相当应另一种常用的模式是adjustResize
,意为调整(压缩)模式,他会把界面中的空间压缩,如果有ScrollView,会让ScrollView自动滚动到合适的位置,以完全显示键盘。
由于我的需求不是使用adjustPan
模式,所以我直接使用adjustResize
模式,AndroidManifest.xml中的声明如下:
android:windowSoftInputMode="stateHidden|adjustResize"
然后就出现了键盘遮挡输入框的现象,如图所示,之前可以看到[备注1]和[备注2]被挡住了。
为解决这种情况,需要给顶层布局嵌入一个ScrollView,然后再来测试下。
截图如下所示,可以看到“基本正常”了,点击输入框会把空白区域压缩,并滑动ScrollView,没有什么大问题,但是只要仔细观察,还是可以看出一点不合理的地方,键盘顶着输入法光标的下边缘,但是实际上ScrollView可以再往下滑动一点点的。
1.3 解决输入框的一点点遮挡的问题
输入法键盘遮挡了[备注1]的EditText下面一点点,实际上键盘是在光标的正下方紧贴着显示地,但是此时的UI交互体验较差,应该让键盘弹起时,ScrollView往上多滑动一点点。
1.3.1 fullScroll
刚开始使用比较笨的方法,检测EditText获得焦点时,把ScrollView滑动到最底端,在网上搜索看到ScrollView滑动到最底层的代码是:
scrollView.fullScroll(View.FOCUS_DOWN);
完整代码
extra.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if (b) {
scrollView.fullScroll(View.FOCUS_DOWN);
}
}
});
执行代码,发现[备注1]的输入框无法输入文字了,来看看fullScroll最终调用的地方:
public boolean fullScroll(int direction) {
boolean down = direction == View.FOCUS_DOWN;
int height = getHeight();
mTempRect.top = 0;
mTempRect.bottom = height;
if (down) {
int count = getChildCount();
if (count > 0) {
View view = getChildAt(count - 1);
mTempRect.bottom = view.getBottom() + mPaddingBottom;
mTempRect.top = mTempRect.bottom - height;
}
}
return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);
}
最后会执行scrollAndFocus(direction, mTempRect.top, mTempRect.bottom)
,
private boolean scrollAndFocus(int direction, int top, int bottom) {
boolean handled = true;
int height = getHeight();
int containerTop = getScrollY();
int containerBottom = containerTop + height;
boolean up = direction == View.FOCUS_UP;
View newFocused = findFocusableViewInBounds(up, top, bottom);
if (newFocused == null) {
newFocused = this;
}
if (top >= containerTop && bottom <= containerBottom) {
handled = false;
} else {
int delta = up ? (top - containerTop) : (bottom - containerBottom);
doScrollY(delta);
}
if (newFocused != findFocus()) newFocused.requestFocus(direction);
return handled;
}
会把ScrollView当前区域中的可以获焦的View,请求焦点。导致本来属于[备注1]的焦点又失去了,最终的现象就是[备注1]里无法输入内容。
后来将fullScroll
方法替换成scrollTo
或者scrollBy
方法,直接让ScrollView
滑动一个较大值,滑动到底部。后来又发现通过监听输入框焦点变化来调整ScrollView
又不符合要求,在第一次点击输入框时,ScrollView
可以滑动到指定位置,但是此时将键盘收起,再点击输入框弹起键盘时,因为焦点没有发生变化,所以又不能滑动到指定位置了,在网络上搜索,有一个方法
activity_main.getViewTreeObserver().addOnGlobalLayoutListener
将整个布局添加鉴定,键盘弹起来之后可见区域的高度会变小,并认为此现象就是键盘弹起导致的,然后再去滑动ScrollView
。看起来是可以了,但是没有从根本上解决问题,可见区域高度变化超过m,就认为是键盘弹起来了,程序中m写的是100,但是不能保证所有的输入法高度都超过100吧?
于是后来放弃了这种监听键盘弹起,再通过代码滑动ScrollView
的方法。
通过实验,我发现使用默认的EditText
竟然不会出现键盘遮挡一部分输入框的现象,如图所示。
而两者的区别仅仅在于,出问题的EditText设置了背景:
<EditText
android:id="@+id/extra"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="#aaa"
android:hint="备注1"
android:gravity="right|center_vertical" />
<EditText
android:id="@+id/extra"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:hint="备注1"
android:gravity="right|center_vertical" />
通过老王的提示,发现EditText默认使用的背景是:
android:background="@android:drawable/edit_text"
而edit_text.xml中使用的背景是一张.9图片,名为@drawable/textfield_default
,在SDK的目录下搜索图片,找到图片,在AS中打开,可以看到.9图片设置的显示的内容区域上面和下面都有padding。
于是,我仿照着给EditText的上下边距增加Padding值,如下:
<EditText
android:id="@+id/extra"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="#aaa"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:hint="备注1"
android:gravity="right|center_vertical" />
注意高度需要设置为wrap_content
,否则需要计算好高度。
我的理解就是: 键盘弹起的时候上端顶的位置由光标的高度和paddingTop和paddingBottom之和决定。也就是
这里理解的不知道对不对,以后在验证一下。