[转摘]ANDROID自定义输入法-自定义键盘
摘要:在本教程中,将通过一个股票输入法实例来详细介绍如何在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的生命周期。