editview和软键盘详解
由于开发中遇到搜索框和软键盘的问题,于是好好地总结了一下这部分的知识,内容比较全有大量干货。
1.editview焦点变化不会直接调用软键盘的显示和隐藏!!!
一直以来大家可能都有一个误区就是,并且网上关于这里的大部分博客都是互相抄袭,误人子弟,认为editiview的焦点变化会直接影响软键盘的显示和隐藏,最典型的观点就是,我点击editview出现光标,软键盘就弹出来了,不就说明了一旦editview获取焦点,就能调用软键盘了吗?一般来说当editview被点击时,会自动调用软键盘,但是本身editview焦点的获取与否不会影响键盘的显示或者隐藏,两者本身没有联系。看下面的例子。
这是MainActivity的xml文件,很简单就是一个editview边上有一个button,简单地模仿常见的搜索框。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_containter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".MainActivity">
<EditText
android:id="@+id/et"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="text"
/>
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:text="搜索"
/>
</LinearLayout>
这是java代码,主要就是设置了button的点击事件,当被点击时,会请求获取焦点,并在通过setOnFocusChangeListener监听button的焦点变化事件,用log打印出来检验是否button此时获取了焦点。实现看一下是否editview焦点变化,会导致软键盘的调用和隐藏。并且让editview的焦点清空。
这里还有一个知识点就是我们在看别人写代码设置焦点的时候,通常会加上通,设置了
setFocusable让是这个控件是否能获得焦点
setFocusableInTouchMode作用是让控件能够被通过触摸来获取焦点
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
EditText editText;
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.et);
button = findViewById(R.id.btn);
button.setFocusableInTouchMode(true);
button.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
Log.d(TAG, "focused");
} else {
Log.d(TAG, "unfocused");
}
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
button.requestFocus();
}
});
}
}
第一张图时点击editview时会导致软键盘的弹出,似乎验证了之前的想法,但是先别高兴地太早。
下图是点击button以后可以看到log打印出focsed,说明此时焦点已经变化到button上,但是软键盘并没有隐藏。
再将代码改一下
点击按钮使editview获取焦点,此时再看一下变化。
editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
Log.d(TAG, "focused");
} else {
Log.d(TAG, "unfocused");
}
}
});
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
editText.requestFocus();
}
});
可以很清楚地看到此时editview获取了焦点,但是软键盘并没有调用
因此我们可以得出一个结论就是: editview的焦点变化,不会导致软键盘发生变化
这时候回到前面的观点,为什么我点击editview 获取了焦点,软键盘就会弹出来呢?
再回到代码里看源码,editview的代码很简单找了一圈发现没有和这部分有关系的内容,因此再看它的父类textview。代码很多,我们直接搜索关键字,在这里剧透一下后面的内容,软键盘的显示可以通过showSoftInput()方法。因此我们搜一下这个方法的名字,果不其然找到了这个方法的几处调用。其中有一处的调用是在onTouchEvent,将多余的代码省略,直接看最终的内容 imm.showSoftInput(this, 0)。
到这里我们基本就可以明白了,原来是textview内的onTouchEvent调用了软键盘的显示,而当用户触摸editview时会调用onTouchEvent,因此才会给我们一种editview获取焦点就会调用软键盘的错觉。
@Override
public boolean onTouchEvent(MotionEvent event) {
…………
if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) {
imm.showSoftInput(this, 0);
}
// The above condition ensures that the mEditor is not null
mEditor.onTouchUpEvent(event);
handled = true;
}
return superResult;
}
至此真相大白。
2.editview的不同状态
通常我们可以注意到,软键盘的enter键在不同的业务情况下,显示不同的样式以及有不同的功能。比如最常见的搜索框出现的软键盘就是一个放大镜的符号。
可以通过xml中的imeOptions属性或者进行设置setImeOptions()进行设置不同的。具体的显示效果根据手机的厂商和机型以及系统语言决定,这里不做过多的展示,自己可以用手机进行测试一下看看效果。注意要设置android:singleLine或者android:inputType才会生效,具体原因不清楚,有知道的欢迎指正。
imeOptions有几种值,分别如下所示
- actionSearch enter键是一个搜索标志或者文字,点击后如果是最后一个输入框则会收起软键盘,如果下面还有自动跳到下个搜索框。
- actionDone enter键是一个完成标志或者文字,点击后不管是不是最后一个都会收起软键盘。
- actionNext enter键是下一步,点击后跳到下一个editview,如果是最后一个则会回到上面第一个,如此循环,永远不会收起软键盘。
- actionPrevious 和actionNext正好相反一对,会网上一个editview跳转。
- actionNone 会自动跳转到下一个editview,永远不会收起软键盘
- actionGo 用于跳转另一个界面,多个editview时,效果同actionSearch。
- actionSend 常见于微信、短信等需要发送消息等界面,效果同actionSearch。
- actionUnspecified 是一个比较意思的值,imeOptions默认的值。它是由系统决定属于以上的哪种值,有点七十二变的意思,当然系统毕竟没有那么智能,不然也不需要上面罗列的那些值了,我目前发现只有以下的两种情况,一般情况等于actionNext,如果在最后一个等于actionDone。当然也有可能有别的情况欢迎补充。
注意: 上面imeOptions的几种值只是修改enter键显示内容,并没有实际的功能,比如说搜索按钮,还是需要自己监听点击事件做对应的处理才行,至于如何自定义修改enter键显示的内容,我仔细找了一下没有发现好的办法,如果有知道的欢迎解答。
3.软键盘的显示和隐藏
那么既然editview焦点的变化没办法控制软键盘的隐藏和显示,那么我们如何能够控制它的显示和隐藏呢?先说结论有两种方式,可以通过showSoftInput/hideSoftInputFromWindow 和toggleSoftInput。
(1)showSoftInput/hideSoftInputFromWindow
先说怎么使用。
//软键盘的显示,注意这个view就是用来接受软键盘输入的,必须是已经获取焦点的
//注意尽量不要在onCreate中直接调用,为什么在后面讲述。
imm.showSoftInput(button, 0);
//软件盘的隐藏
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(),0);
需要注意的是这个已经获取焦点,比如下面这么通过button的点击事件来调用软键盘,此时没有焦点,你需要点两次才能调用软键盘,第一次是获取焦点,第二次才是调用软键盘。
button2.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
imm.showSoftInput(button2, 0);
}
});
所以我一般的写法通常是和焦点变化的监听函数绑定在一起,像这样写。
button.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override public void onFocusChange(View v, boolean hasFocus) {
if(hasFocus){
Log.d(TAG,"et focused");
imm.showSoftInput(button, 0);
}else {
Log.d(TAG,"et unfocused");
imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
}
}
});
再来看源码
/**
* Synonym for {@link #showSoftInput(View, int, ResultReceiver)} without
* a result receiver: explicitly request that the current input method's
* soft input area be shown to the user, if needed.
*
* @param view The currently focused view, which would like to receive
* soft keyboard input.
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #SHOW_IMPLICIT} bit set.
*/
public boolean showSoftInput(View view, int flags) {
// Re-dispatch if there is a context mismatch.
final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
if (fallbackImm != null) {
return fallbackImm.showSoftInput(view, flags);
}
return showSoftInput(view, flags, null);
}
具体如何实现的这里就不多说了,主要看一下参数,可以看到有两个参数。第一个参数是view,即用来接受软键盘输入内容的view,注意这个view此时必须要能获取到焦点,否则就会失效。
很多时候发现showSoftInput方法没用,就是因为你在onCreate中调用,此时还没重绘完成,自然无法调用软键盘,所以不能在onCreate中调用。如果一定要在onCreate中调用,可以采取回调的方法,监听点击事件或者延时等到绘制完成后执行,这个后面再介绍。
第二个参数是showflag,这个标识位和后面hideflag一起说。
隐藏的方法是hideSoftInputFromWindow
/**
* Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}
* without a result: request to hide the soft input window from the
* context of the window that is currently accepting input.
*
* @param windowToken The token of the window that is making the request,
* as returned by {@link View#getWindowToken() View.getWindowToken()}.
* @param flags Provides additional operating flags. Currently may be
* 0 or have the {@link #HIDE_IMPLICIT_ONLY} bit set.
*/
public boolean hideSoftInputFromWindow(IBinder windowToken, int flags) {
return hideSoftInputFromWindow(windowToken, flags, null);
}
可以看到也有一个flag,这里简单地说一下这两个flag位。下面是源码。
/**
* Flag for {@link #showSoftInput} to indicate that this is an implicit
* request to show the input window, not as the result of a direct request
* by the user. The window may not be shown in this case.
*/
public static final int SHOW_IMPLICIT = 0x0001;
/**
* Flag for {@link #showSoftInput} to indicate that the user has forced
* the input method open (such as by long-pressing menu) so it should
* not be closed until they explicitly do so.
*/
public static final int SHOW_FORCED = 0x0002;
/**
* Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
* to indicate that the soft input window should only be hidden if it was not explicitly shown
* by the user.
*/
public static final int HIDE_IMPLICIT_ONLY = 0x0001;
/**
* Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)}
* to indicate that the soft input window should normally be hidden, unless it was originally
* shown with {@link #SHOW_FORCED}.
*/
public static final int HIDE_NOT_ALWAYS = 0x0002;
虽然代码中只写了有四个常量,但是实际上还有两个0,一共有6个值,这里不去纠结每个常量具体什么意思,因为确实很难理解,我也没搞明白,只需要知道大致的使用场景,这里学习的这篇博文,
Android手动显示和隐藏软键盘方法总结。
这里借用一下里面的表,T代表能隐藏,F代表不能
参数 | 0 | SHOW_IMPLICIT | SHOW_FORCED |
0 | T | T | T |
HIDE_IMPLICIT_ONLY | F | T | F |
HIDE_NOT_ALWAYS | T | T | F |
大致总结一下就是:这些标识位对显示没有影响,只对隐藏有影响。举个例子就是如果我显示用的SHOW_IMPLICIT,那么不管隐藏用哪种标识位,都可以隐藏。但是如果用的SHOW_FORCED进行显示,那么只有0才可以隐藏
(2)toggleSoftInput()
用法很简单,而且不需要区分隐藏和显示。
button2.requestFocus();
button2.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
imm.toggleSoftInput(0,0);
}
源码如下所示,因为这里飘红跳转不到具体的方法,只能看一下这里的注释说明。
/**
* This method toggles the input method window display.
*
* If the input window is already displayed, it gets hidden.
* If not the input window will be displayed.
* @param showFlags Provides additional operating flags. May be
* 0 or have the {@link #SHOW_IMPLICIT},
* {@link #SHOW_FORCED} bit set.
* @param hideFlags Provides additional operating flags. May be
* 0 or have the {@link #HIDE_IMPLICIT_ONLY},
* {@link #HIDE_NOT_ALWAYS} bit set.
*/
public void toggleSoftInput(int showFlags, int hideFlags) {
if (mCurMethod != null) {
try {
mCurMethod.toggleSoftInput(showFlags, hideFlags);
} catch (RemoteException e) {
}
}
}
大概就是这个方法会触发输入法的展示,如果已经显示就隐藏,如果隐藏就显示。两个参数分别是显示和隐藏的flag位。
4.如何在onCreate中调用?
如何实现进activity就调用软键盘?如果你用上面的方式,发现是没用的,因为此时调用软键盘的控件还没加载成功,可以采用下面
可以使用延时执行
InputMethodManager imm;
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button.postDelayed(new Runnable() {
@Override
public void run() {
imm.showSoftInput(editText, 0);
}
}, 100);
}
或者用setSoftInputMode()
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.et);
imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE );
editText.requestFocus();
关于这个方法setSoftInputMode这个方法,可以简单的看一下参数 。
有几个值,
SOFT_INPUT_STATE_UNSPECIFIED 未指定,由系统判断是否弹出来,一般需要有editview加上scrollview的组合才会弹。
SOFT_INPUT_STATE_UNCHANGED 保留上一个界面的软键盘状态,换句话说就是上一个显示下一个就显示。
SOFT_INPUT_STATE_VISIBLE
SOFT_INPUT_STATE_ALWAYS_VISIBLE
这两个在进入这个界面的时候都会弹出软键盘,但他们的区别在于从别的界面返回这个界面的时候,前者不会弹出来,后者会弹出来。
SOFT_INPUT_STATE_HIDDEN
SOFT_INPUT_STATE_ALWAYS_HIDDEN
和上面这组这好相反,这两个进入界面的时候都不会弹软键盘,区别在于返回这个界面时,前者会显示软键盘,后者会隐藏。
简单试验一下。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = findViewById(R.id.et);
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE );
//分别用两个参数试验一下,发现进入这个界面的时候都会弹出软键盘,但是返回这个界面的时候,
//SOFT_INPUT_STATE_VISIBLE不会弹,而SOFT_INPUT_STATE_ALWAYS_VISIBLE会弹
// getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE );
imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
editText.requestFocus();
button=findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
Intent intent=new Intent(MainActivity.this,MainActivity2.class);
startActivity(intent);
}
});
}
还有一组以adjust开头的值,它们此时不再影响软键盘的显示和隐藏而是影响软键盘弹起后对屏幕控件对占用,听起来很抽象接着往下看。
SOFT_INPUT_ADJUST_UNSPECIFIED 由系统决定取哪种值,似乎无论滚动与否都等于SOFT_INPUT_ADJUST_PAN。
SOFT_INPUT_ADJUST_PAN 会将屏幕的内容忘上挤,以防输入框被软键盘覆盖
SOFT_INPUT_ADJUST_Resize 会覆盖屏幕下方的位置。
如果屏幕是滚动的,则都会表现为SOFT_INPUT_ADJUST_PAN的情况。
SOFT_INPUT_ADJUST_NOTHING 不做任何处理
不知道为什么和网上的说法有点不太一样?