输入法编辑器 (IME) 是一种用户控件,允许用户输入文本。Android 提供了一个可扩展的输入法框架,允许应用程序为用户提供替代输入方法,例如屏幕键盘或语音输入。安装 IME 后,用户可以从系统设置中选择一个,并在整个系统中使用它。一次只能启用一个 IME。
要向 Android 系统添加 IME,请创建一个包含扩展 InputMethodService
类的 Android 应用程序。此外,您通常会创建一个“设置”活动,将选项传递给 IME 服务。您还可以定义一个设置 UI,该 UI 显示为系统设置的一部分。
此页面涵盖以下主题
如果您以前没有使用过 IME,请先阅读入门文章 屏幕输入法。
IME 生命周期
下图描述了 IME 的生命周期
以下部分描述了如何实现与遵循此生命周期的 IME 相关的 UI 和代码。
在清单中声明 IME 组件
在 Android 系统中,IME 是一个包含特殊 IME 服务的 Android 应用程序。应用程序的清单文件必须声明该服务,请求必要的权限,提供与操作 action.view.InputMethod
匹配的意图过滤器,并提供定义 IME 特性的元数据。此外,为了提供一个允许用户修改 IME 行为的设置界面,您可以定义一个可以从系统设置启动的“设置”活动。
以下代码片段声明了一个 IME 服务。它请求权限 BIND_INPUT_METHOD
以允许服务将 IME 连接到系统,设置与操作 android.view.InputMethod
匹配的意图过滤器,并为 IME 定义元数据
<!-- 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 的设置活动。它具有 ACTION_MAIN
的意图过滤器,这表明此活动是 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>
您还可以直接从 IME 的 UI 访问其设置。
输入法 API
IME 的特定类位于 android.inputmethodservice
和 android.view.inputmethod
包中。 KeyEvent
类对于处理键盘字符非常重要。
IME 的核心部分是一个服务组件——一个扩展 InputMethodService
的类。除了实现正常的服务生命周期外,此类还具有回调函数,用于提供 IME 的 UI、处理用户输入以及将文本传递到具有焦点的字段。默认情况下, InputMethodService
类提供了管理 IME 的状态和可见性以及与当前输入字段通信的大部分实现。
以下类也很重要
BaseInputConnection
- 定义了从
InputMethod
返回到接收其输入的应用程序的通信通道。您可以使用它来读取光标周围的文本,将文本提交到文本框,并将原始键盘事件发送到应用程序。应用程序必须扩展此类,而不是实现基本接口InputConnection
。 KeyboardView
- 一个扩展
View
的类,它呈现键盘并响应用户输入事件。键盘布局由Keyboard
的实例指定,您可以在 XML 文件中定义它。
设计输入法 UI
IME 有两个主要的视觉元素:输入视图和候选视图。您只需实现与您正在设计的输入法相关的元素。
输入视图
输入视图是用户以按键点击、手写或手势形式输入文本的 UI。首次显示 IME 时,系统将调用 onCreateInputView()
回调。在您的此方法实现中,创建您要在 IME 窗口中显示的布局并将布局返回到系统。以下代码片段显示了实现 onCreateInputView()
方法的示例
Kotlin
override fun onCreateInputView(): View { return layoutInflater.inflate(R.layout.input, null).apply { if (this is MyKeyboardView) { setOnKeyboardActionListener(this@MyInputMethod) keyboard = latinKeyboard } } }
Java
@Override public View onCreateInputView() { MyKeyboardView inputView = (MyKeyboardView) getLayoutInflater().inflate(R.layout.input, null); inputView.setOnKeyboardActionListener(this); inputView.setKeyboard(latinKeyboard); return inputView; }
在此示例中, MyKeyboardView
是 KeyboardView
的自定义实现的实例,它呈现 Keyboard
。
候选视图
候选视图是 IME 显示潜在的单词更正或建议供用户选择的 UI。在 IME 生命周期中,系统在准备好显示候选视图时调用 onCreateCandidatesView()
。在您的此方法实现中,返回显示单词建议的布局,或者如果不想显示任何内容,则返回 null。null 响应是默认行为,因此如果您不提供建议,则无需实现此方法。
UI 设计注意事项
本节描述了一些 IME 的 UI 设计注意事项。
处理多种屏幕尺寸
IME 的 UI 必须能够针对不同的屏幕尺寸进行缩放,并能够处理横向和纵向方向。在非全屏 IME 模式下,请为应用程序留出足够的空间来显示文本字段和任何相关的上下文,以便 IME 占据的屏幕空间不超过一半。在全屏 IME 模式下,这不是问题。
处理不同的输入类型
Android 文本字段允许您设置特定的输入类型,例如自由格式文本、数字、URL、电子邮件地址和搜索字符串。当您实现新的 IME 时,检测每个字段的输入类型并为其提供适当的界面。但是,您不必设置 IME 来检查用户是否为输入类型输入了有效文本。这是拥有文本字段的应用程序的责任。
例如,以下是 Latin IME 为 Android 平台文本输入提供的界面
以下是 Latin IME 为 Android 平台数字输入提供的界面
当输入字段获得焦点且您的输入法启动时,系统会调用onStartInputView()
,并传入一个包含输入类型和文本字段其他属性详细信息的EditorInfo
对象。在这个对象中,inputType
字段包含文本字段的输入类型。
inputType
字段是一个int
类型,包含各种输入类型设置的位模式。要测试文本字段的输入类型,请使用常量TYPE_MASK_CLASS
对其进行掩码,如下所示
Kotlin
inputType and InputType.TYPE_MASK_CLASS
Java
inputType & InputType.TYPE_MASK_CLASS
输入类型位模式可以具有多个值,包括:
TYPE_CLASS_NUMBER
- 用于输入数字的文本字段。如图3所示,拉丁输入法会为这种类型的字段显示数字键盘。
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
的变体,用于输入网址和其他统一资源标识符 (URI)。TYPE_TEXT_FLAG_AUTO_COMPLETE
TYPE_CLASS_TEXT
的变体,用于输入应用程序可以从字典、搜索或其他工具自动完成的文本。
在测试这些变体时,请使用适当的常量对inputType
进行掩码。InputType
的参考文档中列出了可用的掩码常量。
将文本发送到应用程序
当用户使用您的输入法输入文本时,您可以通过发送单个按键事件或编辑应用程序文本字段周围的文本来将文本发送到应用程序。无论哪种情况,都使用InputConnection
的实例来传递文本。要获取此实例,请调用InputMethodService.getCurrentInputConnection()
。
编辑光标周围的文本
在处理现有文本的编辑时,BaseInputConnection
中一些有用的方法如下:
-
getTextBeforeCursor()
- 返回一个包含当前光标位置之前请求数量字符的
CharSequence
。 -
getTextAfterCursor()
- 返回一个包含当前光标位置之后请求数量字符的
CharSequence
。 -
deleteSurroundingText()
- 删除当前光标位置之前和之后指定数量的字符。
-
commitText()
- 将
CharSequence
提交到文本字段并设置新的光标位置。
例如,以下代码片段显示了如何将光标左侧的四个字符替换为文本“Hello!”。
Kotlin
currentInputConnection.also { ic: InputConnection -> ic.deleteSurroundingText(4, 0) ic.commitText("Hello", 1) ic.commitText("!", 1) }
Java
InputConnection ic = getCurrentInputConnection(); ic.deleteSurroundingText(4, 0); ic.commitText("Hello", 1); ic.commitText("!", 1);
支持在提交前组合文本
如果您的输入法预测文本或需要多个步骤才能组合字形或单词,您可以在用户提交单词之前在文本字段中显示进度,然后您可以用完整的文本替换部分组合。在将文本传递给setComposingText()
时,您可以通过添加span对其进行特殊处理。
以下代码片段演示了如何在文本字段中显示进度。
Kotlin
currentInputConnection.also { ic: InputConnection -> ic.setComposingText("Composi", 1) ic.setComposingText("Composin", 1) ic.commitText("Composing ", 1) }
Java
InputConnection ic = getCurrentInputConnection(); ic.setComposingText("Composi", 1); ic.setComposingText("Composin", 1); ic.commitText("Composing ", 1);
拦截硬件按键事件
即使输入法窗口没有显式焦点,它也会首先接收硬件按键事件,并且可以消费这些事件或将它们转发到应用程序。例如,您可能希望使用方向键在组合期间的UI中导航以进行候选选择。您可能还希望拦截后退键以关闭源自输入法窗口的任何对话框。
要拦截硬件按键,请覆盖onKeyDown()
和onKeyUp()
。
对于您不想自己处理的按键,请调用super()
方法。
创建输入法子类型
子类型允许输入法公开输入法支持的多种输入模式和语言。子类型可以表示以下内容:
- 语言环境,例如 en_US 或 fr_FR
- 输入模式,例如语音、键盘或手写
- 输入法特有的其他输入样式、形式或属性,例如 10 键或 QWERTY 键盘布局
模式可以是任何文本,例如“键盘”或“语音”。子类型还可以公开这些内容的组合。
子类型信息用于从通知栏和输入法设置中提供的输入法切换对话框。此信息还允许框架直接调出输入法的特定子类型。构建输入法时,请使用子类型功能,因为它可以帮助用户识别和切换不同的输入法语言和模式。
在输入法的 XML 资源文件中之一中使用<subtype>
元素定义子类型。以下代码片段定义了一个具有两个子类型的输入法:一个用于美国英语语言环境的键盘子类型,另一个用于法国的法语语言环境的键盘子类型。
<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:languageTag="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:languageTag="fr-FR" android:imeSubtypeMode="keyboard" android:imeSubtypeExtraValue="someVariable=30,someInternalOption=false" /> <subtype android:name="@string/display_name_german_keyboard_ime" ... /> </input-method>
为了确保您的子类型在UI中正确标记,请使用`%s`来获取与子类型的语言环境标签相同的子类型标签。接下来的两个代码片段对此进行了演示。第一个代码片段显示了输入法XML文件的一部分。
<subtype android:label="@string/label_subtype_generic" android:imeSubtypeLocale="en_US" android:icon="@drawable/icon_en_us" android:imeSubtypeMode="keyboard" />
下一个代码片段是输入法的strings.xml
文件的一部分。输入法UI定义用于设置子类型标签的字符串资源label_subtype_generic
定义如下:
<string name="label_subtype_generic">%s</string>
此设置会导致子类型的显示名称与语言环境设置匹配。例如,在任何英语语言环境中,显示名称为“英语(美国)”。
从通知栏选择输入法子类型
Android系统管理所有输入法公开的所有子类型。输入法子类型被视为其所属输入法的模式。用户可以从通知栏或“设置”应用导航到可用输入法子类型的菜单,如下图所示。
从系统设置中选择输入法子类型
用户还可以控制在系统设置中的语言和输入设置面板中如何使用子类型。
在输入法子类型之间切换
您可以通过提供切换键(例如键盘上的球形语言图标)让用户轻松地在输入法子类型之间切换。这可以提高键盘的可用性,并为用户带来便利。要启用此切换功能,请执行以下步骤:
- 在输入法的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告诉系统平等对待所有子类型,无论它们属于哪个输入法。指定true要求系统循环遍历当前输入法中的子类型。
一般的输入法注意事项
在实现输入法时,还需要考虑以下其他事项:
- 提供一种方法,让用户直接从输入法的UI设置选项。
- 提供一种方法,让用户直接从输入法UI切换到不同的输入法,因为设备上可能安装了多个输入法。
- 快速调出输入法的UI。预加载或按需加载任何大型资源,以便用户在点击文本字段后立即看到输入法。缓存资源和视图以供后续调用输入法。
- 在隐藏输入法窗口后立即释放大型内存分配,以便应用程序有足够的内存运行。如果输入法隐藏了几秒钟,请使用延迟消息释放资源。
- 确保用户可以为与输入法关联的语言或语言环境输入尽可能多的字符。用户可能会在密码或用户名中使用标点符号,因此您的输入法必须提供许多不同的字符,以允许用户输入密码并访问设备。