在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>
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;
}
}
}
在 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", "----------------------------------------------");
Android源碼中,對EditorInfo定義了常用的IME常量值:
public static final int IME_ACTION_UNSPECIFIED = ;
public static final int IME_ACTION_NONE = ;
public static final int IME_ACTION_GO = ;
public static final int IME_ACTION_SEARCH = ;
public static final int IME_ACTION_SEND = ;
public static final int IME_ACTION_NEXT = ;
public static final int IME_ACTION_DONE = ;
public static final int IME_ACTION_PREVIOUS = ;
下面是設定不同android:imeOptions時軟鍵盤Enter鍵顯示的不同效果和點選之後系統的列印資訊
1. android:imeOptions=”actionDone”
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyMzkDMyUTM3ETMwcDM1EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
圖檔右下角顯示”完成
在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 = ;
沒有特别指定要進行什麼動作,由編輯器自己指定,當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 = ;
指定為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);
}
如果actionId 沒有特别指定的話,就會被預設為IME_NULL,再檢視EditorInfo源碼發現:
/**
* Generic unspecified type for {@link #imeOptions}.
*/
public static final int IME_NULL = ;
在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(), );
}
return;
}
}
ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
long eventTime = SystemClock.uptimeMillis();
viewRootImpl.dispatchKeyFromIme(
new KeyEvent(eventTime, eventTime,
KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, , ,
KeyCharacterMap.VIRTUAL_KEYBOARD, ,
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, , ,
KeyCharacterMap.VIRTUAL_KEYBOARD, ,
KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
| KeyEvent.FLAG_EDITOR_ACTION));
}
}
系統會首先判斷使用者實作的方法ict.onEditorActionListener.onEditorAction(this, actionCode, null)的傳回值,一旦傳回true,會立即return,是以系統的處理被直接跳過。
如果想自己實作部分功能,然後其他的基本操作由系統完成,那就在實作方法onEditorAction()是傳回false。