在android发开过程中,有时候需要对EditText的软键盘进行监听。
当点击软键盘回车位置按键的时候,需要实现 完成、前进、下一项、搜索、发送或其他功能,这就需要开发者对软键盘回车的点击事件进行捕捉。
比如在登录界面,需要用户在输入密码之后点击软键盘回车直接登录,不必再去点击屏幕上的登录按钮。我们就可以在密码使用的EditText设置 android:imeOptions=”actionDone”,然后对EditText设置OnEditorActionListener监听,当捕捉到用户点击完成时,调用登录方法即可。(IME英文全称Input Method Editors,中文名称输入法编辑器)
先看一个demo
布局文件如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context=".MainActivity">
<ScrollView
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<EditText
android:id="@+id/actionDoneEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:inputType="text"
android:imeOptions="actionDone"
android:hint="actionDone" />
<EditText
android:id="@+id/actionGoEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:inputType="text"
android:imeOptions="actionGo"
android:hint="actionGo" />
<EditText
android:id="@+id/actionNextEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:inputType="text"
android:imeOptions="actionNext"
android:hint="actionNext" />
<EditText
android:id="@+id/actionNoneEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:inputType="text"
android:imeOptions="actionNone"
android:hint="actionNone" />
<EditText
android:id="@+id/actionPreviousEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:inputType="text"
android:imeOptions="actionPrevious"
android:hint="actionPrevious" />
<EditText
android:id="@+id/actionSearchEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:inputType="text"
android:imeOptions="actionSearch"
android:hint="actionSearch" />
<EditText
android:id="@+id/actionUnspecifiedEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:inputType="text"
android:imeOptions="actionUnspecified"
android:hint="actionUnspecified" />
<EditText
android:id="@+id/actionSendEditText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:inputType="text"
android:imeOptions="actionSend"
android:hint="actionSend" />
</LinearLayout>
</ScrollView>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
Attention One:
使用android:imeOptions属性的时候,一定要对EditText设置 android:inputType 或者 设置 android:singleline=”true”。
在activity_main.xml文件中,定义了8个EditText,imeOptions分别是:
actionDone 完成 对应 EditorInfo.IME_ACTION_DONE
actionGo 前进 对应 EditorInfo.IME_ACTION_GO
actionNext 下一项 对应 EditorInfo.IME_ACTION_NEXT
actionNone 无动作 对应 EditorInfo.IME_ACTION_NONE
actionPrevious 上一项 对应 EditorInfo.IME_ACTION_PREVIOUS
actionSearch 搜索 对应 EditorInfo.IME_ACTION_SEARCH
actionUnspecified 未指定 对应 EditorInfo.IME_ACTION_UNSPECIFIED
actionSend 发送 对应 EditorInfo.IME_ACTION_SEND
在MainActivity中:
public class MainActivity extends Activity implements TextView.OnEditorActionListener {
private EditText mActionDoneEditText;
private EditText mActionGoEditText;
private EditText mActionNextEditText;
private EditText mActionNoneEditText;
private EditText mActionPreviousEditText;
private EditText mActionSearchEditText;
private EditText mActionSendEditText;
private EditText mActionUnspecifiedEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mActionDoneEditText = (EditText) findViewById(R.id.actionDoneEditText);
mActionGoEditText = (EditText) findViewById(R.id.actionGoEditText);
mActionNextEditText = (EditText) findViewById(R.id.actionNextEditText);
mActionNoneEditText = (EditText) findViewById(R.id.actionNoneEditText);
mActionPreviousEditText = (EditText) findViewById(R.id.actionPreviousEditText);
mActionSearchEditText = (EditText) findViewById(R.id.actionSearchEditText);
mActionSendEditText = (EditText) findViewById(R.id.actionSendEditText);
mActionUnspecifiedEditText = (EditText) findViewById(R.id.actionUnspecifiedEditText);
mActionDoneEditText.setOnEditorActionListener(this);
mActionGoEditText.setOnEditorActionListener(this);
mActionNextEditText.setOnEditorActionListener(this);
mActionNoneEditText.setOnEditorActionListener(this);
mActionPreviousEditText.setOnEditorActionListener(this);
mActionSearchEditText.setOnEditorActionListener(this);
mActionSendEditText.setOnEditorActionListener(this);
mActionUnspecifiedEditText.setOnEditorActionListener(this);
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
doWhichOperation(actionId);
Log.e("BALLACK", "event: " + event);
Log.e("BALLACK", "v.getImeActionId(): " + v.getImeActionId());
Log.e("BALLACK", "v.getImeOptions(): " + v.getImeOptions());
Log.e("BALLACK", "----------------------------------------------");
return true;
}
private void doWhichOperation(int actionId) {
switch (actionId) {
case EditorInfo.IME_ACTION_DONE:
Log.e("BALLACK", "IME_ACTION_DONE");
break;
case EditorInfo.IME_ACTION_GO:
Log.e("BALLACK", "IME_ACTION_GO");
break;
case EditorInfo.IME_ACTION_NEXT:
Log.e("BALLACK", "IME_ACTION_NEXT");
break;
case EditorInfo.IME_ACTION_NONE:
Log.e("BALLACK", "IME_ACTION_NONE");
break;
case EditorInfo.IME_ACTION_PREVIOUS:
Log.e("BALLACK", "IME_ACTION_PREVIOUS");
break;
case EditorInfo.IME_ACTION_SEARCH:
Log.e("BALLACK", "IME_ACTION_SEARCH");
break;
case EditorInfo.IME_ACTION_SEND:
Log.e("BALLACK", "IME_ACTION_SEND");
break;
case EditorInfo.IME_ACTION_UNSPECIFIED:
Log.e("BALLACK", "IME_ACTION_UNSPECIFIED");
break;
default:
break;
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
在 onEditorAction方法中先通过doWhichOperation(actionId)判断点击的是什么操作,然后打印相关操作信息
Log.e("BALLACK", "event: " + event);
Log.e("BALLACK", "v.getImeActionId(): " + v.getImeActionId());
Log.e("BALLACK", "v.getImeOptions(): " + v.getImeOptions());
Log.e("BALLACK", "----------------------------------------------");
- 1
- 2
- 3
- 4
Android源码中,对EditorInfo定义了常用的IME常量值:
public static final int IME_ACTION_UNSPECIFIED = 0x00000000;
public static final int IME_ACTION_NONE = 0x00000001;
public static final int IME_ACTION_GO = 0x00000002;
public static final int IME_ACTION_SEARCH = 0x00000003;
public static final int IME_ACTION_SEND = 0x00000004;
public static final int IME_ACTION_NEXT = 0x00000005;
public static final int IME_ACTION_DONE = 0x00000006;
public static final int IME_ACTION_PREVIOUS = 0x00000007;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
下面是设置不同android:imeOptions时软键盘回车键显示的不同效果和点击之后系统的打印信息
1. android:imeOptions=”actionDone”
图片右下角显示”完成
在Log信息中,可以看到,通过actionId匹配到EditorInfo.IME_ACTION_DONE,v.getImeOptions()=6
actionId和 EditorInfo.IME_ACTION_DONE 可以一一对应。
2. android:imeOptions=”actionGo”
图片右下角显示”前往
在Log信息中,可以看到,通过actionId匹配到EditorInfo.IME_ACTION_GO,v.getImeOptions()=2
actionId和 EditorInfo.IME_ACTION_GO可以一一对应。
3. android:imeOptions=”actionNext”
图片右下角显示”下一项
在Log信息中,可以看到,通过actionId匹配到EditorInfo.IME_ACTION_NEXT,v.getImeOptions()=5
actionId和 EditorInfo.IME_ACTION_NEXT可以一一对应。
4. android:imeOptions=”actionPrevious”
图片右下角显示”上一项
在Log信息中,可以看到,通过actionId匹配到EditorInfo.IME_ACTION_PREVIOUS,v.getImeOptions()=7,actionId和 EditorInfo.IME_ACTION_PREVIOUS可以一一对应。
5. android:imeOptions=”actionSearch”
图片右下角显示”搜索
在Log信息中,可以看到,通过actionId匹配到EditorInfo.IME_ACTION_SEARCH,v.getImeOptions()=3,actionId和 EditorInfo.IME_ACTION_SEARCH可以一一对应。
6. android:imeOptions=”actionSend”
图片右下角显示”发送
在Log信息中,可以看到,通过actionId匹配到EditorInfo.IME_ACTION_SEND,v.getImeOptions()=4,actionId和 EditorInfo.IME_ACTION_SEND可以一一对应。
7. android:imeOptions=”actionUnspecified”
图片右下角显示”下一项
在Log信息中,可以看到,通过actionId匹配到EditorInfo.IME_ACTION_NEXT,v.getImeOptions()=0,actionId和 EditorInfo.IME_ACTION_UNSPECIFIED无法对应上。
8. android:imeOptions=”actionNone”
图片右下角显示”回车
在Log信息中,可以看到,通过actionId匹配到EditorInfo.IME_ACTION_UNSPECIFIED,v.getImeOptions()=1,actionId和 EditorInfo.IME_ACTION_NONE无法对应上,而且出现两次重复的Log信息。
在上述出现的8个不同效果上android:imeOptions=”actionUnspecified”和android:imeOptions=”actionNone”出现了错误的信息,无法和EditorInfo中的值对应上,v.getImeOptions()取得的值和EditorInfo中的常量可以一一对应。
Attention Two:
actionId是系统捕捉到用户点击键盘时取得的数值,而v.getImeOptions()拿到的是在xml文件中对EditText设置的android:imeOptions属性的数值,这就是为什么v.getImeOptions()和EditorInfo永远可以一一对应的原因。
在源码中,对IME_ACTION_UNSPECIFIED定义是:
/**
* Bits of {@link #IME_MASK_ACTION}: no specific action has been
* associated with this editor, let the editor come up with its own if
* it can.
*/
public static final int IME_ACTION_UNSPECIFIED = 0x00000000;
- 1
- 2
- 3
- 4
- 5
- 6
没有特别指定要进行什么动作,由编辑器自己指定,当EditText处于不同的位置的时候,系统自动判断当前EditText可能需要执行什么样的动作。当把指定为actionUnspecified的EditText放在最后面的时候,就会显示为”完成“,打印出来的信息也会是 IME_ACTION_DONE。
同理,IME_ACTION_NONE也是这样:
/**
* Bits of {@link #IME_MASK_ACTION}: there is no available action.
*/
public static final int IME_ACTION_NONE = 0x00000001;
- 1
- 2
- 3
- 4
指定为actionDone的EditText没有指定任何动作,就会被系统默认显示回车。
在TextView的源码中,对接口OnEditoractionListener的定义如下:
/**
* Interface definition for a callback to be invoked when an action is
* performed on the editor.
*/
public interface OnEditorActionListener {
/**
* Called when an action is being performed.
*
* @param v The view that was clicked.
* @param actionId Identifier of the action. This will be either the
* identifier you supplied, or {@link EditorInfo#IME_NULL
* EditorInfo.IME_NULL} if being called due to the enter key
* being pressed.
* @param event If triggered by an enter key, this is the event;
* otherwise, this is null.
* @return Return true if you have consumed the action, else false.
*/
boolean onEditorAction(TextView v, int actionId, KeyEvent event);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
如果actionId 没有特别指定的话,就会被默认为IME_NULL,再查看EditorInfo源码发现:
/**
* Generic unspecified type for {@link #imeOptions}.
*/
public static final int IME_NULL = 0x00000000;
- 1
- 2
- 3
- 4
在EditorInfo中,有两个常量IME_NULL和IME_ACTION_UNSPECIFIED的值都是0x00000000。
android:imeOptions=”actionNone”打印出来的信息出现IME_ACTION_UNSPECIFIED,其实由于没有任何动作,应该是IME_NULL,只是在MainActivity代码中对actionId=0时统一指定为了IME_ACTION_UNSPECIFIED。
Attention Three:
部分第三方的输入法,对EditorInfo支持的不一样,有的功能实现了,但是对应的图标没有修改过来,有的干脆功能就没有实现。比如谷歌自己的输入法,就不支持actionPrevious的图标和功能,而百度输入法的小米专用版中,actionPrevious功能虽然实现了,但是图标仍然显示的是回车的图标。
对EditText指定不同的imeOptions之后,就需要实现OnEditorActionListener 中的onEditorAction()方法,然后根据不同的动作执行进行响应。
对于actionDone、actionNext和actionPrevious,系统都自己进行了部分处理。
- actionDone:隐藏输入法
- actionNext:跳到下一个EditText
- actionPrevious:跳到上一个EditText
然并卵!!!
原因在于,在TextView源码的方法onEditorAction()中:
public void onEditorAction(int actionCode) {
final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
if (ict != null) {
if (ict.onEditorActionListener != null) {
if (ict.onEditorActionListener.onEditorAction(this,
actionCode, null)) {
return;
}
}
// This is the handling for some default action.
// Note that for backwards compatibility we don't do this
// default handling if explicit ime options have not been given,
// instead turning this into the normal enter key codes that an
// app may be expecting.
if (actionCode == EditorInfo.IME_ACTION_NEXT) {
View v = focusSearch(FOCUS_FORWARD);
if (v != null) {
if (!v.requestFocus(FOCUS_FORWARD)) {
throw new IllegalStateException("focus search returned a view " +
"that wasn't able to take focus!");
}
}
return;
} else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
View v = focusSearch(FOCUS_BACKWARD);
if (v != null) {
if (!v.requestFocus(FOCUS_BACKWARD)) {
throw new IllegalStateException("focus search returned a view " +
"that wasn't able to take focus!");
}
}
return;
} else if (actionCode == EditorInfo.IME_ACTION_DONE) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && imm.isActive(this)) {
imm.hideSoftInputFromWindow(getWindowToken(), 0);
}
return;
}
}
ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
long eventTime = SystemClock.uptimeMillis();
viewRootImpl.dispatchKeyFromIme(
new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
| KeyEvent.FLAG_EDITOR_ACTION));
viewRootImpl.dispatchKeyFromIme(
new KeyEvent(SystemClock.uptimeMillis(), eventTime,
KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
| KeyEvent.FLAG_EDITOR_ACTION));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
系统会首先判断用户实现的方法ict.onEditorActionListener.onEditorAction(this, actionCode, null)的返回值,一旦返回true,会立即return,因此系统的处理被直接跳过。
如果想自己实现部分功能,然后其他的基本操作由系统完成,那就在实现方法onEditorAction()是返回false。