前言
最近改 bug 的時候碰到一個小知識點,在搜尋介面希望鍵盤上的 enter 鍵改為搜尋按鈕。也就是下圖的效果,是不是非常常見。
然後我就記得 Editext 有個 imeOptions 的屬性,可以設定 enter 鍵的效果。所以果斷在 xml 中寫下 android:imeOptions="actionSearch"
,然後把問題改為已修復,信心滿滿。結果等編譯執行起來在手機上發現沒有起作用,還是 enter 鍵。 搜尋了一下,發現大家都是這樣回答的:
如果設定了 imeOptions 屬性,鍵盤沒有變化 ,那麼需要單獨加一些其他的屬性配合使用,xml中 屬性設定:
1 將singleLine設定為true
2.或者將inputType設定為text
試了一下,果然 ok 了。但為什麼這樣設定顯示就對了呢?你是不是同樣也有疑問? 所以就讓我們共同去探尋答案吧!
ImeOptions 屬性原始碼解析
這裡首先推薦一個看原始碼的外掛:AndroidSourceView。
Step1
因為如果是在 JAVA 檔案中,我們設定 imeOptions 屬性程式碼為: editText.setImeOptions(EditorInfo.IME_ACTION_SEARCH);
所以首先要定位到 setImeOptions()
這個方法 ,在 EdiText 中沒有搜到,所以果斷跳到 EditText 的父類 TextView 的原始碼中,然後發現目標:
/**
* Change the editor type integer associated with the text view, which
* is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions}
* when it has focus.
* @see #getImeOptions
* @see android.view.inputmethod.EditorInfo
* @attr ref android.R.styleable#TextView_imeOptions
* 當輸入法編譯器(IME)獲取焦點的時候,通過{@link EditorInfo#imeOptions} 報告給輸入法編輯器,
* 來更改與文字檢視關聯的編輯器型別值。
*/
public void setImeOptions(int imeOptions) {
//1.判斷是否需要建立 Editor 物件
createEditorIfNeeded();
//2.判斷是否需要建立 InputContentType
mEditor.createInputContentTypeIfNeeded();
//3. 將入參賦值給InputContentType.imeOptions
mEditor.mInputContentType.imeOptions = imeOptions;
}
複製程式碼
這個方法裡面只是進行了判斷是否需要建立一些物件,然後將我們的入參賦值給 InputContentType.imeOptions。從這方法的註釋中我們可以知道關鍵物件是 EditorInfo#imeOptions
。
Step2
繼續搜尋關鍵字 imeOptions ,然後發現下面這個方法:
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
if (onCheckIsTextEditor() && isEnabled()) {
mEditor.createInputMethodStateIfNeeded();
outAttrs.inputType = getInputType();
if (mEditor.mInputContentType != null) {
outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
outAttrs.actionId = mEditor.mInputContentType.imeActionId;
outAttrs.extras = mEditor.mInputContentType.extras;
outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales;
} else {
outAttrs.imeOptions = EditorInfo.IME_NULL;
outAttrs.hintLocales = null;
}
if (focusSearch(FOCUS_DOWN) != null) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
}
if (focusSearch(FOCUS_UP) != null) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
}
if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
== EditorInfo.IME_ACTION_UNSPECIFIED) {
if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
// An action has not been set, but the enter key will move to
// the next focus, so set the action to that.
outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
} else {
// An action has not been set, and there is no focus to move
// to, so let's just supply a "done" action.
outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
}
if (!shouldAdvanceFocusOnEnter()) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
}
if (isMultilineInputType(outAttrs.inputType)) {
// Multi-line text editors should always show an enter key.
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
outAttrs.hintText = mHint;
if (mText instanceof Editable) {
InputConnection ic = new EditableInputConnection(this);
outAttrs.initialSelStart = getSelectionStart();
outAttrs.initialSelEnd = getSelectionEnd();
outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
return ic;
}
}
return null;
}
複製程式碼
然後我們只關心我們探究的資訊,所以虛擬碼如下:
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
// return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType
outAttrs.inputType = getInputType();
if (mEditor.mInputContentType != null) {
outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
} else {
outAttrs.imeOptions = EditorInfo.IME_NULL;
}
if (focusSearch(FOCUS_DOWN) != null) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
}
if (focusSearch(FOCUS_UP) != null) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
}
if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION)
== EditorInfo.IME_ACTION_UNSPECIFIED) {
//..程式碼省略
}
if (isMultilineInputType(outAttrs.inputType)) {
// Multi-line text editors should always show an enter key.
//多行文字編譯器總是會顯示 enter 鍵
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
return null;
}
複製程式碼
Step3
我們首先分析一下:
- 第一個 if 判斷:
mEditor.mInputContentType
這個值是否為空,答案是 false ,因為我們在setImeOptions
方法的第 3 步已經給它賦值了,所以不會為空,所以outAttrs.imeOptions
結果為我們設定的值 - 第二個 if 判斷:是來尋找我們的 Editext 物件的往下的一個方向是否存在可以獲取焦點的 View,這個得看實際輸入框下面佈局是否還會有一個輸入框,目前專案沒有,所以 false
- 第三個 if 判斷:跟第二個條件一樣,只過是方向為向上查詢
- 第四個 if 判斷:首先
(outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION
值是多少呢,我們程式碼裡設定的值是IME_ACTION_SEARCH
,值為 3,EditorInfo.IME_MASK_ACTION
的值為 255 ,取二進位制值為 00000011 & 1111 1111 = 00000011 十進位制為 3,而EditorInfo.IME_ACTION_UNSPECIFIED
值為 0,所以結果為 false。
到目前為止 outAttrs.imeOptions
的結果依然為我們在程式碼中設定的值。
Step4
來看最後一個 if 判斷:isMultilineInputType(outAttrs.inputType)
光看欄位意思我們能猜到判斷輸入框是不是多行輸入。看一下具體程式碼:
private static boolean isMultilineInputType(int type) {
return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
== (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
}
複製程式碼
type 這個值為我們在 onCreateInputConnection() 方法中通過getInputType () 方法獲取,結果為返回我們程式碼中設定的 inputType 值,而我們程式碼中沒有設定,那麼這個 inputType 的預設值是什麼呢?原始碼中沒有找到,然後我自己 debug 了一下,結果如下圖:
所以預設值為 131073,而 InputType 各種型別的值如下:public interface InputType {
int TYPE_CLASS_DATETIME = 4;
int TYPE_CLASS_NUMBER = 2;
int TYPE_CLASS_PHONE = 3;
int TYPE_CLASS_TEXT = 1;
int TYPE_DATETIME_VARIATION_DATE = 16;
int TYPE_DATETIME_VARIATION_NORMAL = 0;
int TYPE_DATETIME_VARIATION_TIME = 32;
int TYPE_MASK_CLASS = 15;
int TYPE_MASK_FLAGS = 16773120;
int TYPE_MASK_VARIATION = 4080;
int TYPE_NULL = 0;
int TYPE_NUMBER_FLAG_DECIMAL = 8192;
int TYPE_NUMBER_FLAG_SIGNED = 4096;
int TYPE_NUMBER_VARIATION_NORMAL = 0;
int TYPE_NUMBER_VARIATION_PASSWORD = 16;
int TYPE_TEXT_FLAG_AUTO_COMPLETE = 65536;
int TYPE_TEXT_FLAG_AUTO_CORRECT = 32768;
int TYPE_TEXT_FLAG_CAP_CHARACTERS = 4096;
int TYPE_TEXT_FLAG_CAP_SENTENCES = 16384;
int TYPE_TEXT_FLAG_CAP_WORDS = 8192;
int TYPE_TEXT_FLAG_IME_MULTI_LINE = 262144;
int TYPE_TEXT_FLAG_MULTI_LINE = 131072;
int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288;
int TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 32;
int TYPE_TEXT_VARIATION_EMAIL_SUBJECT = 48;
int TYPE_TEXT_VARIATION_FILTER = 176;
int TYPE_TEXT_VARIATION_LONG_MESSAGE = 80;
int TYPE_TEXT_VARIATION_NORMAL = 0;
int TYPE_TEXT_VARIATION_PASSWORD = 128;
int TYPE_TEXT_VARIATION_PERSON_NAME = 96;
int TYPE_TEXT_VARIATION_PHONETIC = 192;
int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 112;
int TYPE_TEXT_VARIATION_SHORT_MESSAGE = 64;
int TYPE_TEXT_VARIATION_URI = 16;
int TYPE_TEXT_VARIATION_VISIBLE_PASSWORD = 144;
int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 160;
int TYPE_TEXT_VARIATION_WEB_EMAIL_ADDRESS = 208;
int TYPE_TEXT_VARIATION_WEB_PASSWORD = 224;
}
複製程式碼
結合 stackoverflow 上 Default inputType of EditText in android 答案和 InputType 的值,也就是說 inputType 預設值 為 InputType.TYPE_CLASS_TEXT|InputType.TYPE_TEXT_FLAG_MULTI_LINE
組合的結果。
然後我們繼續往下看,isMultilineInputType() 方法的返回值
(type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE))
== (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE
複製程式碼
轉換為十進位制為:(131073 & (15 | 131072)) == 1 | 131072
轉換為二進位制為:(100000000000000001 &(1111 | 100000000000000000)) == 1 | 100000000000000000
即 100000000000000001 == 100000000000000001
即 true。
所以會進入最後一個條件中,而該返回值明確指出 多行文字編譯器總是會顯示 enter 鍵 。所以我們會看到我們設定的屬性失效了。
而如果我們設定了 singleLine = true
,原始碼中是這樣設定的:
/**
* Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
* @param singleLine
*/
private void setInputTypeSingleLine(boolean singleLine) {
if (mEditor != null
&& (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS)
== EditorInfo.TYPE_CLASS_TEXT) {
if (singleLine) {
mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
} else {
mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
}
}
}
複製程式碼
如果 singleLine 為 true,inputType = ~ 131072,即011111111111111111 ,最後計算結果為 111111111111111111 == 100000000000000001
,顯然 false。
或者設定了inputType = TYPE_CLASS_TEXT 我們也可以得出返回值為 false,所以不會進判斷條件,也就是 imeOptions 的值就是我們在程式碼裡或者 xml 中設定的。
----------------------------2018/12/05更新-----------------------------------
非常感謝 "與非" 的指正。之前的理解有點小偏差。
我們來看一下最後一步如果滿足了isMultilineInputType() == true
這個條件,就會執行outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
,需要注意的是 IME_FLAG_NO_ENTER_ACTION* 這個 flag,只要設定了這個 flag,就只會顯示 “enter” 鍵。所以想要到達我們要設定的效果,關鍵不是“不能滿足多行輸入的特性”,而是需要清除這個flag,讓這個flag 失效。
所以,就衍生了下面的問題:
如何設定 EditText 支援多行屬性同時我們設定的 imeOptions 效果也能實現? 答案的關鍵就是來清除 IME_FLAG_NO_ENTER_ACTION 這個 flag ,一般用位運算清除。 所以我們首先需要重寫 EditText 的 onCreateInputConnection() 方法,然後清除 IME_FLAG_NO_ENTER_ACTION 這個 flag 。 具體程式碼如下:
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
if(inputConnection != null){
outAttrs.imeOptions &= ~EditorInfo.IME_FLAG_NO_ENTER_ACTION;
}
return inputConnection;
}
複製程式碼
ps: 查這個問題的時候還有種方案是這樣的:
editText.setHorizontallyScrolling(false);
editText.setMaxLines(Integer.MAX_VALUE);
複製程式碼
看了程式碼,並沒有發現其原理。歡迎交流看法。
總結
所以要讓 imeOptions 滿足我們的需求,關鍵是取消 IME_FLAG_NO_ENTER_ACTION 這個標記位。
可以選擇如下處理方式:
1. 設定 singleLine 屬性為 true
2. 或者設定 inputType 屬性。 而這個屬性值不一定非要是 ‘text’ 即TYPE_CLASS_TEXT 型別,只要最後結果不是 131072 (也就是 InputType.TYPE_CLASS_TEXT |InputType.TYPE_TEXT_FLAG_MULTI_LINE 即可,根據需求來定是 設定 text 或者 純數字鍵盤等等)
如果需要同時滿足多行輸入和 設定imeOptins 有效果可以重寫 EditText 的 onCreateInputConnection() 方法,然後清除 IME_FLAG_NO_ENTER_ACTION