摘要:在本教程中,將通過一個股票輸入法實例來詳細介紹如何在OPhone1.5中創建自定義輸入法。
OPhone輸入法介紹
得益於OPhone1.5的良好架構,在OPhone中創建輸入法可不再是一件多么復雜的事情了。下面來看看OPhone中輸入法的組成部分以及如何實現。
圖一:OPhone系統默認輸入法
上圖是OPhone系統內置的輸入法,可以看到一個輸入法具有兩個組成部分。第一是位於下方的輸入軟鍵盤(KeyboardView),用來輸入文 本和符號;第二是軟鍵盤上面的輸入候選區域(CandidateView),通過用戶的輸入提供一些可能的組合讓用戶選擇。要觸發輸入法顯示還需要一個輸 入目標,在上圖的輸入目標就是短消息內容文本,而短消息這個程序就是觸發輸入法的客戶端程序。
軟鍵盤的實現:在OPhone 1.5中軟鍵盤是很容易實現的,通過android.inputmethodservice.Keyboard 類來創建軟鍵盤,該類從XML文件中讀取軟鍵盤信息。有多少行,每行有多少按鍵,每個按鍵代表什么內容 等等。
候選區域的實現:對於中文輸入法來說候選區域是一個特別重要的內容,拿拼音輸入法來說用戶輸入拼音會出現多個候選詞語,通過對候選詞語的合理安排是一個輸入法是否好用的重要評判標准之一。但是也有特殊情況下不需要候選區域的,例如輸入數字或則密碼。候選區域通過繼承View實現。
在OPhone中輸入法是一個服務(android.app.Service),用戶通過點擊輸入目標來觸發該服務,然后顯示輸入法,OPhone系統提 供了一個Service實現android.inputmethodservice.InputMethodService,InputMethodService提供了一些接口方 便實現輸入法。下面就來看看每個部分如何實現,在下面的示例中通過一個股票輸入鍵盤來講解,經常炒股的人都知道在股票軟件中有一些特殊組合輸入,例 如:600、000、A、C、F(在輸入帳戶的時候)。
軟鍵盤實現
通過android.inputmethodservice.Keyboard只需要在XML文件中定義鍵盤布局就可以了,下面是股票軟鍵盤的XML代碼:
- res\xml\stock.xml
- <?xml version="1.0" encoding="utf-8"?>
- <Keyboard xmlns:android="http://schemas.android.com/apk/res/android"
- android:keyWidth="20%p"
- android:horizontalGap="0px"
- android:verticalGap="0px"
- android:keyHeight="@dimen/key_height"
- >
- <Row>
- <Key android:codes="49" android:keyLabel="1"
- android:keyEdgeFlags="left"/>
- <Key android:codes="50" android:keyLabel="2"/>
- <Key android:codes="51" android:keyLabel="3"/>
- <Key android:codes="52" android:keyLabel="4"/>
- <Key android:codes="53" android:keyLabel="5"
- android:keyEdgeFlags="right"/>
- </Row>
- <Row>
- <Key android:codes="54" android:keyLabel="6"
- android:keyEdgeFlags="left"/>
- <Key android:codes="55" android:keyLabel="7"/>
- <Key android:codes="56" android:keyLabel="8"/>
- <Key android:codes="57" android:keyLabel="9"/>
- <Key android:codes="48" android:keyLabel="0"
- android:keyEdgeFlags="right"/>
- </Row>
- <Row>
- <Key android:codes="97" android:keyLabel="a"
- android:keyEdgeFlags="left"/>
- <Key android:codes="99" android:keyLabel="c"/>
- <Key android:codes="102" android:keyLabel="f"/>
- <Key android:codes="46" android:keyLabel="."/>
- <Key android:codes="-5"
- android:keyIcon="@drawable/sym_keyboard_delete"
- android:keyEdgeFlags="right"
- android:isRepeatable="true"/>
- </Row>
- <Row android:rowEdgeFlags="bottom">
- <Key android:codes="-3" android:keyWidth="20%p"
- android:keyIcon="@drawable/sym_keyboard_done"
- android:keyEdgeFlags="left" />
- <Key android:codes="-2" android:keyLabel="123" android:keyWidth="20%p" />
- <Key android:keyOutputText="600" android:keyLabel="600"
- android:keyWidth="20%p" />
- <Key android:keyOutputText="000" android:keyLabel="000"
- android:keyWidth="20%p" />
- <Key android:codes="10" android:keyWidth="20%p"
- android:keyIcon="@drawable/sym_keyboard_return"
- android:keyEdgeFlags="right"/>
- </Row>
- </Keyboard>
res\xml\stock.xml <?xml version="1.0" encoding="utf-8"?> <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:keyWidth="20%p" android:horizontalGap="0px" android:verticalGap="0px" android:keyHeight="@dimen/key_height" > <Row> <Key android:codes="49" android:keyLabel="1" android:keyEdgeFlags="left"/> <Key android:codes="50" android:keyLabel="2"/> <Key android:codes="51" android:keyLabel="3"/> <Key android:codes="52" android:keyLabel="4"/> <Key android:codes="53" android:keyLabel="5" android:keyEdgeFlags="right"/> </Row> <Row> <Key android:codes="54" android:keyLabel="6" android:keyEdgeFlags="left"/> <Key android:codes="55" android:keyLabel="7"/> <Key android:codes="56" android:keyLabel="8"/> <Key android:codes="57" android:keyLabel="9"/> <Key android:codes="48" android:keyLabel="0" android:keyEdgeFlags="right"/> </Row> <Row> <Key android:codes="97" android:keyLabel="a" android:keyEdgeFlags="left"/> <Key android:codes="99" android:keyLabel="c"/> <Key android:codes="102" android:keyLabel="f"/> <Key android:codes="46" android:keyLabel="."/> <Key android:codes="-5" android:keyIcon="@drawable/sym_keyboard_delete" android:keyEdgeFlags="right" android:isRepeatable="true"/> </Row> <Row android:rowEdgeFlags="bottom"> <Key android:codes="-3" android:keyWidth="20%p" android:keyIcon="@drawable/sym_keyboard_done" android:keyEdgeFlags="left" /> <Key android:codes="-2" android:keyLabel="123" android:keyWidth="20%p" /> <Key android:keyOutputText="600" android:keyLabel="600" android:keyWidth="20%p" /> <Key android:keyOutputText="000" android:keyLabel="000" android:keyWidth="20%p" /> <Key android:codes="10" android:keyWidth="20%p" android:keyIcon="@drawable/sym_keyboard_return" android:keyEdgeFlags="right"/> </Row> </Keyboard>
上面的代碼實現的鍵盤界面如下:
在上面的鍵盤定義中,通過Keyboard說明是一個軟鍵盤定義文件,Row元素說明這是一行按鍵的定義,Key元素說明這是一個按鍵的定義。Key元素通過一些屬性來定義每個按鍵,下面是一些常用的屬性介紹:
- Codes:代表按鍵對應的輸出值,可以為unicode值或則逗號(,)分割的多個值,也可以為一個字 符串。在字符串中通過“\\”來轉義特殊字符,例如 '\\n' 或則 '\\uxxxx' 。Codes通常用來定義該鍵的鍵碼,例如上圖中的數字按鍵1對應的為49;如果提供的是逗號分割的多個值則和普通手機輸入鍵盤一樣在多個值之間切換。
- keyLabel:代表按鍵顯示的文本內容。
- keyIcon:代表按鍵顯示的圖標內容,如果指定了該值則在顯示的時候顯示為圖片不顯示文本。
- keyWidth:代表按鍵的寬度,可以為精確值或則相對值,對於精確值支持多種單位,例如:像素,英寸 等;相對值為相對於基礎取值的百分比,為以% 或則%p 結尾,其中%p表示相對於父容器。
- keyHeight:代表按鍵的高度,取值同上。
- horizontalGap:代表按鍵前的間隙(水平方向),取值同上。
- isSticky:指定按鍵是否為sticky的。例如Shift大小寫切換按鍵,具有兩種狀態,按下狀態和正常狀態,取值為true或則false。
- isModifier:指定按鍵是否為功能鍵( modifier key ) ,例如 Alt 或則 Shift 。取值為true或則false。
- keyOutputText:指定按鍵輸出的文本內容,取值為字符串。
- isRepeatable:指定按鍵是否是可重復的,如果長按該鍵可以觸發重復按鍵事件則為true,否則為false。
- keyEdgeFlags:指定按鍵的對齊指令,取值為left或則right。
在OPhone默認輸入法中,如果統一頁面有多個輸入框,則軟鍵盤中的enter鍵為變為下一個特殊按鍵,點擊該按鍵可以導航到下一個輸入框中,這樣可以方便用戶輸入操作。要實現該功能可以通過自定義Keyboard來實現。 src\org\goodev\ime\StockKeyboard.java
- publicclass StockKeyboard extends Keyboard {
- private Key mEnterKey;
- public StockKeyboard(Context context, int xmlLayoutResId) {
- super(context, xmlLayoutResId);
- }
- public StockKeyboard(Context context, int layoutTemplateResId,
- CharSequence characters, int columns, int horizontalPadding) {
- super(context, layoutTemplateResId, characters, columns, horizontalPadding);
- }
- @Override
- protected Key createKeyFromXml(Resources res, Row parent, int x, int y,
- XmlResourceParser parser) {
- Key key = new Key(res, parent, x, y, parser);
- if (key.codes[0] == 10) {
- mEnterKey = key;
- }
- return key;
- }
- /**
- * 根據輸入狀態,設置enter按鍵的顯示內容。
- */
- void setImeOptions(Resources res, int options) {
- if (mEnterKey == null) {
- Log.d("StockKeyBoard: ", "enterkey == null");
- return;
- }
- switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) {
- case EditorInfo.IME_ACTION_GO:
- mEnterKey.iconPreview = null;
- mEnterKey.icon = null;
- mEnterKey.label = res.getText(R.string.label_go_key);
- break;
- case EditorInfo.IME_ACTION_NEXT:
- mEnterKey.iconPreview = null;
- mEnterKey.icon = null;
- mEnterKey.label = res.getText(R.string.label_next_key);
- break;
- case EditorInfo.IME_ACTION_SEARCH:
- mEnterKey.icon = res.getDrawable(
- R.drawable.sym_keyboard_search);
- mEnterKey.label = null;
- break;
- case EditorInfo.IME_ACTION_SEND:
- mEnterKey.iconPreview = null;
- mEnterKey.icon = null;
- mEnterKey.label = res.getText(R.string.label_send_key);
- break;
- default:
- mEnterKey.icon = res.getDrawable(
- R.drawable.sym_keyboard_return);
- mEnterKey.label = null;
- break;
- }
- }
- }
public class StockKeyboard extends Keyboard { private Key mEnterKey; public StockKeyboard(Context context, int xmlLayoutResId) { super(context, xmlLayoutResId); } public StockKeyboard(Context context, int layoutTemplateResId, CharSequence characters, int columns, int horizontalPadding) { super(context, layoutTemplateResId, characters, columns, horizontalPadding); } @Override protected Key createKeyFromXml(Resources res, Row parent, int x, int y, XmlResourceParser parser) { Key key = new Key(res, parent, x, y, parser); if (key.codes[0] == 10) { mEnterKey = key; } return key; } /** * 根據輸入狀態,設置enter按鍵的顯示內容。 */ void setImeOptions(Resources res, int options) { if (mEnterKey == null) { Log.d("StockKeyBoard: ", "enterkey == null"); return; } switch (options&(EditorInfo.IME_MASK_ACTION|EditorInfo.IME_FLAG_NO_ENTER_ACTION)) { case EditorInfo.IME_ACTION_GO: mEnterKey.iconPreview = null; mEnterKey.icon = null; mEnterKey.label = res.getText(R.string.label_go_key); break; case EditorInfo.IME_ACTION_NEXT: mEnterKey.iconPreview = null; mEnterKey.icon = null; mEnterKey.label = res.getText(R.string.label_next_key); break; case EditorInfo.IME_ACTION_SEARCH: mEnterKey.icon = res.getDrawable( R.drawable.sym_keyboard_search); mEnterKey.label = null; break; case EditorInfo.IME_ACTION_SEND: mEnterKey.iconPreview = null; mEnterKey.icon = null; mEnterKey.label = res.getText(R.string.label_send_key); break; default: mEnterKey.icon = res.getDrawable( R.drawable.sym_keyboard_return); mEnterKey.label = null; break; } } }
軟鍵盤是放到KeyboardView中的,這里也自定義一個KeyboardView實現來處理特殊按鍵事件: src\org\goodev\ime\StockKeyboardView.java
- publicclass StockKeyboardView extends KeyboardView {
- publicstaticfinalint KEYCODE_OPTIONS = -100;
- public StockKeyboardView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
- public StockKeyboardView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- }
- /**
- * 覆寫這個方法,當用戶長按CANCEL鍵的時候 拋出事件,可以用來現實現實輸入法選項的操作
- */
- @Override
- protectedboolean onLongPress(Key key) {
- if (key.codes[0] == Keyboard.KEYCODE_CANCEL) {
- getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null);
- returntrue;
- } else {
- returnsuper.onLongPress(key);
- }
- }
- }
public class StockKeyboardView extends KeyboardView { public static final int KEYCODE_OPTIONS = -100; public StockKeyboardView(Context context, AttributeSet attrs) { super(context, attrs); } public StockKeyboardView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * 覆寫這個方法,當用戶長按CANCEL鍵的時候 拋出事件,可以用來現實現實輸入法選項的操作 */ @Override protected boolean onLongPress(Key key) { if (key.codes[0] == Keyboard.KEYCODE_CANCEL) { getOnKeyboardActionListener().onKey(KEYCODE_OPTIONS, null); return true; } else { return super.onLongPress(key); } } }
候選區域實現
示例中的候選區域只是擴展View的簡單實現,具體實現可以參考附件中的src\org\goodev\ime\CandidateView.java代碼。
定義輸入法服務
通過擴展android.inputmethodservice.InputMethodService可以很容易的實現一個輸入法服 務,InputMethodService提供了一些系統回調函數,可以安裝需要來實現。在具體實現之前,先來了解下InputMethodService的生命周期。