输入法编辑器(IME)是能够让用户输入文字的一个空间,Android提供了一个可扩展的输入法框架,它允许应用程序为用户提供可选的输入法,如基于触屏的键盘输入法或基于语音的输入法。安装自己喜欢的输入法后,用户就可以在系统设置中选择一个输入法,并在以后的各种场景中使用此输入法;一次只能选择一个输入法。
如果要为Android系统增加一款IME, 需要创建一个Android应用,该应用中包含了一个继承了InputMethodService的类。此外通常还要创建一个“settings”Activity,把选项传递给IME服务。也可以定义一个用于设置的UI,让它做为系统设置的一部分来显示。
本指南包括如下内容:
- IME的生命周期
- 在AndroidManifest中生命IME的组件
- IME的API
- 设计IME的UI
- 从IME中发送文本给应用程序
- IME子类型的使用
如果此前没有IME的工作经验,应该首先阅读 Onscreen Input Methods这篇文章,此外,可以通过修改SDK中SoftKeyboard应用的样例代码建立自己的IME应用。
IME的生命周期
下图描述了IME的生命周期
图 1. IME的生命周期
一下章节介绍如何实现UI以及和这个生命周期相关联的代码。
在Manifest中生命IME的组件
在Android系统中,IME是一个包含了特定的IME服务的Android应用程序。在应用程序的AndroidManifest必须声明服务、申请必要的权限、提供跟action.view.InputMethod操作相匹配的InterFilter、以及定义IME的特性的metadata。此外还要停工一个允许用户修改IME行为的设置接口,可以定义一个能从系统设置中加载的“settings”activity。
下面是一个生命IME service的生命片段。其申请了BIND_INPUT_METHOD以便允许把该service连接IME到系统中,建立了一个匹配 android.view.InputMethod action的intent filter,并且为这个IME定义了metadata。
<!-- Declares the input method service -->
<service android:name="FastInputIME"
android:label="@string/fast_input_label"
android:permission="android.permission.BIND_INPUT_METHOD">
<intent-filter>
<action android:name="android.view.InputMethod" />
</intent-filter>
<meta-data android:name="android.view.im"
android:resource="@xml/method" />
</service>
接下来的片段生命了IME的settings activity。它有一个ACTION_MAIN的intent filter,其指明了这个activity是这个IME应用的主入口:
<!-- Optional: an activity for controlling the IME settings -->
<activity android:name="FastInputIMESettings"
android:label="@string/fast_input_settings">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
也可以从它的UI中提供一个直接访问这个IME settings的接口。
输入法的API
针对IME的类在android.inputmethodservice和android.view.inputmethod包中,KeyEvent类是处理键盘字符的重要类。
IME的核心部分是一个service组件,一个继承自InputMethodService的类。除了要执行service的正常生命周期外,该类还为IME的UI提供了一些回调,用来处理用户的输入,分发文本到当前具有焦点域的组件。默认情况下,InputMethodService类实现了大部分的管理的IME的状态和IME的可见情况以及和当前输入域的通信。
一下类也很重要:
BaseInputConnection
定义了从输入法返回到接收输入数据的应用的通信通道。通过它读取光标周围的文本,提交文本到文本框中,发送原始的按键事件到应用中。应用应该继承此类而不是直接实现InputConnection的接口。
KeyboardView
View的子类,负责键盘的渲染和响应用户的输入事件。键盘布局就是Keyboard的一个实例,可以通过在XML文件中定义。
设计输入法UI
输入法有两个主要的视觉元素:输入视图和候选视图。仅仅需要执行正在设计的输入需要的元素。
输入视图
输入视图是用户通过以按键,手写或手势等形式输入文本UI元素。当IME第一次显示时,系统会回调onCreateInputView() ,在执行这个方法时,需要创建你想让IME显示的窗口布局并将这个布局返回给系统。下面是onCreateInputView()方法的代码片段:
@Override
public View onCreateInputView() {
MyKeyboardView inputView =
(MyKeyboardView) getLayoutInflater().inflate( R.layout.input, null);
inputView.setOnKeyboardActionListener(this);
inputView.setKeyboard(mLatinKeyboard);
return mInputView;
}
本例中,MyKeyboardView是一个自定义的继承KeyboardView类的实例,负责键盘的渲染。如果想创建传统的QWERTY键盘,请参考KeyboardView类。
候选视图
候选视图显示给用户潜在的单词更正或是给用户建议的文字的UI元素。在IME的生命周期中,当要显示候选视图时,系统会回调onCreateCandidatesView()方法,在这个函数中,返回一个展示单词建议的布局,如果不想显示任何东西,直接返回null。如果不想给用户提供任何建议,返回null是默认的行为。
参见SoftKeyboard样例app中onCreateCandidatesView()方法的实现。
UI设计时的注意事项
本节描述IME设计UI时的一些注意事项。
不同屏幕尺寸的处理
IME的UI必须能够对不同的屏幕尺寸进行缩放,而且也必须能够处理横屏和竖屏。在非全屏的IME模式,需要保留足够的显示文本框及其相关联内容的空间,所以IME占用的屏幕空间不能超过屏幕的一半,在全屏IME模式下,没有这个问题。
不同输入类型的处理
Android的文本框允许用户设置特定的输入类型,想自由形式的文本输入,数字,URL网址,email地址和搜索字符串。当你实现一个新的 IME是,需要检测每个文本框的输入类型并为它提供相应的接口。然而,没有必要检查用户键入的文本类型是否有效,这是相应文本框的应用的责任。
下面是一个Android平台Latin IME的截屏,它提供了文本和电话号码的输入:
图2. Latin IME输入类型
当输入框收到焦点并且IME开启后,系统将回调onStartInputView()函数,并EditorInfo类型的参数,该参数包含了输入类型的细节和文本框的一些其它属性。
inputType域是包含各种输入类型设置的位掩码模式,如果要测试特定的文本框的输入类型,通过掩码进行测试,如:
inputType & InputType.TYPE_MASK_CLASS
输入类型的位掩码的值如下:
- TYPE_CLASS_NUMBER
需要键入数字的文本框。像前面Latin IME的截屏, 为这种类型显示一个数字键盘 - TYPE_CLASS_DATETIME
在该文本框中键入日期和时间。 - TYPE_CLASS_PHONE
在该文本框中键入电话号码。 - TYPE_CLASS_TEXT
在该文本框中键入所有支持的字符。
这些常量的更多 细节请参考InputType的文档。
inputType字段可以包含其它位指示文本字段类型的变体,如:
- TYPE_TEXT_VARIATION_PASSWORD
TYPE_CLASS_TEXT 的变体,用来键入密码,输入法将会显示符号代替真实的文本。 - TYPE_TEXT_VARIATION_URI
TYPE_CLASS_TEXT的变体,用来键入web网址和其他URI。 - TYPE_TEXT_FLAG_AUTO_COMPLETE
TYPE_CLASS_TEXT的变体,从词典,搜索或其他设施中”自动补全”。
记住,当测试这些变量时,用合适的常量掩码测试inputType的值,InputType文档中列出了可用的掩码常量。
注意: 在你的输入法中,确保正确处理了发向密码文本框的文本,在UI和输入视图和候选视图中隐藏密码。另外记得不得在设备上存储密码,可用参考安全设计指南了解更多的信息。
从IME中发送文本给应用程序
当用户通过IME输入文字时,可以通过发送单个的按键事件或者通过编译文本框光标位置的周围文本,将文本发送给应用程序。这两种方法都需要通过InputConnection分发文本。通过调用InputMethodService.getCurrentInputConnection()可以得到InputConnection的实例。
编辑光标周围的文本
当处理在文本框中已经纯在的文本是,一些在BaseInputConnection中更加有用的方法是:
- getTextBeforeCursor()
返回包含当前光标位置之前要求的字符数的CharSequence。 - getTextAfterCursor()
返回包含当前光标位置之后要求的字符数的CharSequence。 - deleteSurroundingText()
删除指定数目的当前字符之前和之后的字符。 - commitText()
提交一个CharSequence的文本字段,并设置一个新的光标位置。
例如下面的片段展示了怎样将当前光标左侧的四个字符替换成”Hello!”:
InputConnection ic = getCurrentInputConnection();
ic.deleteSurroundingText(4, 0);
ic.commitText("Hello", 1);
ic.commitText("!", 1);
提交之前组合文本
如果IME做了文本预测或需要多个步骤来组成字形或单词,在用户提交文本之前,可以在文本框中显示这个组合的过程,然后可以用完成的文本替换部分组合。可以通过给setComposingText()传递一定的文本来完成这一过程。
下面的代码片段展示了如何在文本框中显示这个过程:
InputConnection ic = getCurrentInputConnection();
ic.setComposingText("Composi", 1);
...
ic.setComposingText("Composin", 1);
...
ic.commitText("Composing ", 1);
下面的截图展示了呈献给用户的过程:
图3. 提交之前组合文本
拦截硬件按键事件
即使输入法窗口没有明确的焦点,也能从硬件中接收到按键事件并能够选择是消耗掉他们或者是将他们转发给应用程序。例如,我们可能想使用方向键浏览候选框中的文本,这个方向键就被IME直接消耗掉,不去转发给应用程序。可能还想捕获返回键以关闭源自输入法窗口的任何弹出窗口。
重写onKeyDown()和onKeyUp()去拦截硬件按键,请参阅SoftKeyboard的示例。
记住要为你不想处理的键调用super()方法。
创建IME子类型
子类型允许IME暴露多种其支持的输入模式和语言。子类型可以表示如下:
- 区域,如en_US or fr_FR
- 输入模式,如语音,键盘或手写
- 其它的输入风格,如表单,或IME特定的属性,如10键或qwerty键盘形式。
基本上,模式可以是诸如“键盘”,“语音”等之类。子类型也可以以这些组合的形式暴露给用户。
子类型信息用于IME切换器对话框,该对话框可从通知栏和IME设置中调用。该信息还允许framework直接调出IME特定的子类型。当构建IME时,使用子类型工具,因为它有助于用户识别和在不同的IME语言和模式之间切换。
您可以使用元素在输入法的XML资源文件之一中定义子类型。以下代码片段定义了一个具有两个子类型的IME:美国英语语言环境的键盘子类型,法国的法语语言环境的另一个键盘子类型:
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.softkeyboard.Settings"
android:icon="@drawable/ime_icon"
<subtype android:name="@string/display_name_english_keyboard_ime"
android:icon="@drawable/subtype_icon_english_keyboard_ime"
android:imeSubtypeLanguage="en_US"
android:imeSubtypeMode="keyboard"
android:imeSubtypeExtraValue="somePrivateOption=true"
/>
<subtype android:name="@string/display_name_french_keyboard_ime"
android:icon="@drawable/subtype_icon_french_keyboard_ime"
android:imeSubtypeLanguage="fr_FR"
android:imeSubtypeMode="keyboard"
android:imeSubtypeExtraValue="foobar=30,someInternalOption=false"
/>
<subtype android:name="@string/display_name_german_keyboard_ime"
...
/>
/>
要确保在UI中正确标记子类型,请使用%s获取与子类型的语言环境标签相同的子类型标签。接下来的两个代码片段中会演示器用法。第一个片段显示输入法XML文件的一部分:
<subtype
android:label="@string/label_subtype_generic"
android:imeSubtypeLocale="en_US"
android:icon="@drawable/icon_en_us"
android:imeSubtypeMode="keyboard" />
接下来的一个代码片段是IME的strings.xml文件的一部分。字符串资源label_subtype_generic,由输入法UI定义用来设置子类型的标签,定义为:
<string name="label_subtype_generic">%s</string>
此设置会使子类型的显示名称与区域设置相匹配。例如,在任何英语区域设置中,显示名称为“英语(美国)”。
从通知栏中选择IME子类型
Android系统管理所有IME公开的所有子类型。 IME子类型被视为它们所属的IME的模式。在通知栏中,用户可以为当前设置的IME选择可用的子类型,如以下屏幕截图所示:
图4. 从通知栏中选择IME的子类型
图5. 在系统设置中设置子类型首选项
从系统设置选择IME子类型
用户可以在“系统设置”区域的“语言和输入”设置面板中控制子类型的使用方式。在SoftKeyboard示例中,文件InputMethodSettingsFragment.java包含一个在IME设置中实现子类型启用程序的实现。有关如何在IME中支持输入法子类型的更多信息,请参阅Android SDK中的SoftKeyboard示例程序。
图6 为IME选择语言
在IME子类型之间切换
通过提供切换键,如球形语言图片(可以将其作为输入法组件的一部分),可以允许用户在多个IME子类型中轻松切换。这样做大大提高了键盘的可用性,提高了用户的体验性。要启用此类切换,请执行以下步骤:
- 在输入法的XML资源文件中声明supportsSwitchingToNextInputMethod = “true”。如:
<input-method xmlns:android="http://schemas.android.com/apk/res/android"
android:settingsActivity="com.example.softkeyboard.Settings"
android:icon="@drawable/ime_icon"
android:supportsSwitchingToNextInputMethod="true">
- 调用shouldOfferSwitchingToNextInputMethod()方法。
- 如果该方法返回true, 显示切换键。
- 当用户点击切换键时,调用switchToNextInputMethod(),第二个参数设为false。值false表示系统平等对待所有子类型,而不管它们属于什么IME。指定true要求系统在当前IME中循环遍历子类型。
注意:在Android 5.0(API level 21)之前,switchToNextInputMethod()不知道supportsSwitchingToNextInputMethod属性。如果用户在没有切换键的情况下切换到IME,用户可能会卡在该IME中,不能容易地切换。
通用的IME注意事项
以下是实现IME时需要考虑的其他事项:
- 为用户提供一种直接从IME的UI进入设置选项的方法。
- 可能有多个IME可以安装在设备上,提供一种用于用户从输入法UI直接切换到不同的IME的方式。
- 快速启动IME的UI。根据需要预加载或加载任何大型资源,以便用户在点击文本字段时及时地看到IME。缓存资源和视图,以便输入法的后续调用。
- 相反地,您应该在隐藏输入法窗口后立即释放大量内存分配,以便应用程序可以有足够的内存来运行。如果IME处于隐藏状态几秒钟,请考虑使用延迟消息来释放资源。
- 请确保用户可以为与IME关联的语言或区域设置输入尽可能多的字符。请记住,用户可以在密码或用户名中使用标点符号,因此您的IME必须提供许多不同的字符,以允许用户输入密码并访问设备。