创建输入法

输入法编辑器 (IME) 是一种用户控件,允许用户输入文本。Android 提供了一个可扩展的输入法框架,允许应用程序为用户提供替代输入方法,例如屏幕键盘或语音输入。安装 IME 后,用户可以选择一个从系统设置中使用它在整个系统中。一次只能启用一个 IME。

要将 IME 添加到 Android 系统,请创建一个包含一个扩展 InputMethodService 的类的 Android 应用程序。此外,您通常会创建一个“设置”活动,将选项传递给 IME 服务。您还可以定义一个设置 UI,作为系统设置的一部分显示。

此页面涵盖以下主题

如果您以前没有使用过 IME,请先阅读介绍性文章 屏幕输入法

IME 生命周期

以下图表描述了 IME 的生命周期

An image showing the life cycle of an IME.
图 1. IME 的生命周期。

以下部分描述了如何实现与遵循此生命周期的 IME 相关的 UI 和代码。

在清单中声明 IME 组件

在 Android 系统中,IME 是一个 Android 应用程序,其中包含一个特殊的 IME 服务。应用程序的清单文件必须声明该服务,请求必要的权限,提供与操作 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 提供对 IME 设置的访问。

输入法 API

特定于 IME 的类位于 android.inputmethodserviceandroid.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;
}

在此示例中,MyKeyboardViewKeyboardView 的自定义实现的实例,它渲染一个 Keyboard

候选词视图

候选词视图是 IME 显示潜在的单词更正或建议以供用户选择的 UI。在 IME 生命周期中,系统会在准备好显示候选词视图时调用 onCreateCandidatesView()。在您对该方法的实现中,返回一个显示单词建议的布局,或者如果不想显示任何内容,则返回 null。null 响应是默认行为,因此如果您不提供建议,则不必实现此方法。

UI 设计注意事项

本节介绍了 IME 的一些 UI 设计注意事项。

处理多种屏幕尺寸

IME 的 UI 必须能够适应不同的屏幕尺寸并处理横向和纵向两种方向。在非全屏 IME 模式下,为应用程序保留足够的空间来显示文本字段和任何相关上下文,这样 IME 占用的屏幕空间不会超过一半。在全屏 IME 模式下,这不是问题。

处理不同的输入类型

Android 文本字段允许您设置特定的输入类型,例如自由格式文本、数字、URL、电子邮件地址和搜索字符串。在实现新的 IME 时,检测每个字段的输入类型并为其提供相应的界面。但是,您不必设置 IME 来检查用户是否为输入类型输入了有效文本。这是拥有文本字段的应用程序的职责。

例如,以下是 Latin IME 为 Android 平台文本输入提供的界面

An image showing a text input on a Latin IME
图 2. Latin IME 文本输入。

以下是 Latin IME 为 Android 平台数字输入提供的界面

An image showing a numeric input on a Latin IME
图 3. Latin IME 数字输入。

当输入字段获得焦点并且您的 IME 启动时,系统会调用 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 所示,Latin IME 为此类字段显示一个数字键盘。
TYPE_CLASS_DATETIME
用于输入日期和时间的文本字段。
TYPE_CLASS_PHONE
用于输入电话号码的文本字段。
TYPE_CLASS_TEXT
用于输入任何支持字符的文本字段。

这些常量在 InputType 的参考文档中有更详细的说明。

inputType 字段可以包含其他位,指示文本字段类型的变体,例如

TYPE_TEXT_VARIATION_PASSWORD
TYPE_CLASS_TEXT 的变体,用于输入密码。输入法显示dingbats而不是实际文本。
TYPE_TEXT_VARIATION_URI
TYPE_CLASS_TEXT 的变体,用于输入 Web URL 和其他统一资源标识符 (URI)。
TYPE_TEXT_FLAG_AUTO_COMPLETE
用于输入应用程序从字典、搜索或其他工具自动完成的文本的 TYPE_CLASS_TEXT 变体。

在测试这些变体时,使用适当的常量屏蔽 inputType。可用的掩码常量在 InputType 的参考文档中列出。

将文本发送到应用程序

当用户使用您的 IME 输入文本时,您可以通过发送单个按键事件或编辑应用程序文本字段中光标周围的文本,将文本发送到应用程序。在这两种情况下,都使用 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);

支持在提交之前组合文本

如果您的 IME 预测文本或需要多个步骤来组合字形或单词,您可以在用户提交单词之前在文本字段中显示进度,然后您可以用完成的文本替换部分组合。您可以通过在将文本传递给 setComposingText() 时添加跨度来对文本进行特殊处理。

以下代码段演示了如何在文本字段中显示进度

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() 方法。

创建 IME 子类型

子类型允许 IME 公开 IME 支持的多种输入模式和语言。子类型可以表示以下内容

  • 区域设置,例如 en_US 或 fr_FR
  • 输入模式,例如语音、键盘或手写
  • 其他特定于 IME 的输入样式、表单或属性,例如 10 键或 QWERTY 键盘布局

模式可以是任何文本,例如“键盘”或“语音”。子类型还可以公开这些内容的组合。

子类型信息用于 IME 切换器对话框,该对话框可从通知栏和 IME 设置中获取。该信息还允许框架直接调出 IME 的特定子类型。在构建 IME 时,请使用子类型设施,因为它有助于用户识别和切换不同的 IME 语言和模式。

在输入法的 XML 资源文件中之一中使用 <subtype> 元素定义子类型。以下代码段定义了一个 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: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" />

下一个代码片段是 IME 的 strings.xml 文件的一部分。字符串资源 label_subtype_generic(由输入法 UI 定义用于设置子类型的标签)定义如下

<string name="label_subtype_generic">%s</string>

此设置会导致子类型的显示名称与区域设置匹配。例如,在任何英语区域设置中,显示名称为“英语(美国)”。

从通知栏选择 IME 子类型

Android 系统管理所有 IME 公开的子类型。IME 子类型被视为其所属的 IME 的模式。用户可以从通知栏或设置应用程序导航到可用的 IME 子类型菜单,如下面的图所示

An image showing the Languages & input System menu
图 4. 语言和输入 系统菜单。

从系统设置选择 IME 子类型

用户还可以在系统设置中的 语言和输入 设置面板中控制子类型的使用方式

An image showing the Languages selection menu
图 5. 语言 系统菜单

在 IME 子类型之间切换

您可以通过提供一个切换键(例如键盘上的地球仪形状的语言图标)来让用户轻松地在 IME 子类型之间切换。这将提高键盘的可用性,并且对用户来说很方便。要启用此切换,请执行以下步骤

  1. 在输入法的 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">
    
  2. 调用 shouldOfferSwitchingToNextInputMethod() 方法。
  3. 如果该方法返回 true,则显示一个切换键。
  4. 当用户点击切换键时,调用 switchToNextInputMethod(),传递 false。false 值告诉系统平等地对待所有子类型,而不管它们属于哪个 IME。指定 true 要求系统循环遍历当前 IME 中的子类型。

一般的 IME 注意事项

在实现 IME 时,还要考虑以下其他事项

  • 提供一种方法供用户直接从 IME 的 UI 中设置选项。
  • 提供一种方法供用户直接从输入法 UI 中切换到另一个 IME,因为设备上可能安装了多个 IME。
  • 快速调出 IME 的 UI。预加载或按需加载任何大型资源,以便用户在点击文本字段时立即看到 IME。缓存资源和视图以备输入法的后续调用。
  • 在输入法窗口隐藏后立即释放大型内存分配,以便应用程序有足够的内存运行。如果 IME 隐藏了几秒钟,请使用延迟消息来释放资源。
  • 确保用户可以为与 IME 关联的语言或区域设置输入尽可能多的字符。用户可能在密码或用户名中使用标点符号,因此您的 IME 必须提供许多不同的字符,以允许用户输入密码并访问设备。