创建输入法

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

要将 IME 添加到 Android 系统中,需要创建一个 Android 应用,其中包含一个扩展 InputMethodService 的类。此外,您通常还会创建一个“设置”Activity,用于向 IME 服务传递选项。您还可以定义一个设置界面,将其作为系统设置的一部分显示。

本页涵盖以下主题

如果您之前没有使用过 IME,请先阅读入门文章 屏幕输入法

IME 生命周期

下图描述了 IME 的生命周期

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

以下部分介绍了如何实现遵循此生命周期的 IME 的关联界面和代码。

在清单中声明 IME 组件

在 Android 系统中,IME 是一个包含特殊 IME 服务的 Android 应用。应用的清单文件必须声明该服务,请求必要的权限,提供与 action.view.InputMethod 操作匹配的 intent filter,并提供定义 IME 特性的元数据。此外,为了提供一个设置界面,让用户可以修改 IME 的行为,您可以定义一个“设置”Activity,它可以从系统设置中启动。

以下代码段声明了一个 IME 服务。它请求 BIND_INPUT_METHOD 权限,以允许服务将 IME 连接到系统,设置一个与 android.view.InputMethod 操作匹配的 intent filter,并为 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 的设置 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>

您还可以直接从 IME 的界面访问其设置。

输入法 API

IME 特有的类位于 android.inputmethodserviceandroid.view.inputmethod 包中。KeyEvent 类对于处理键盘字符非常重要。

IME 的核心部分是一个服务组件,即一个扩展 InputMethodService 的类。除了实现正常的 Service 生命周期外,此类还包含提供 IME 界面、处理用户输入以及将文本传递给具有焦点的字段的回调。默认情况下,InputMethodService 类提供了用于管理 IME 状态和可见性以及与当前输入字段通信的大部分实现。

以下类也很重要

BaseInputConnection
定义了从 InputMethod 回到接收其输入的应用的通信通道。您可以使用它来读取光标周围的文本,将文本提交到文本框,并将原始按键事件发送到应用。应用必须扩展此类,而不是实现基本接口 InputConnection
KeyboardView
它是 View 的扩展,用于呈现键盘并响应用户输入事件。键盘布局由 Keyboard 的实例指定,您可以在 XML 文件中定义它。

设计输入法界面

IME 主要有两个视觉元素:输入视图和候选词视图。您只需实现与您正在设计的输入法相关的元素即可。

输入视图

输入视图是用户以按键、手写或手势形式输入文本的界面。首次显示 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 显示供用户选择的潜在词语更正或建议的界面。在 IME 生命周期中,系统准备好显示候选词视图时,会调用 onCreateCandidatesView()。在此方法的实现中,返回显示词语建议的布局,如果您不想显示任何内容,则返回 null。null 响应是默认行为,因此如果您不提供建议,则无需实现此方法。

界面设计注意事项

本节介绍了一些针对 IME 的界面设计注意事项。

处理多种屏幕尺寸

您的 IME 界面必须能够适应不同的屏幕尺寸并处理横向和纵向方向。在非全屏 IME 模式下,为应用显示文本字段和任何相关上下文留出足够的空间,以便 IME 占用的屏幕不超过一半。在全屏 IME 模式下,这不是问题。

处理不同的输入类型

Android 文本字段允许您设置特定的输入类型,例如自由格式文本、数字、网址、电子邮件地址和搜索字符串。当您实现新的 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 字段包含文本字段的输入类型。

IME 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 的参考文档中有更详细的描述。

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

TYPE_TEXT_VARIATION_PASSWORD
TYPE_CLASS_TEXT 的变体,用于输入密码。输入法会显示符号而不是实际文本。
TYPE_TEXT_VARIATION_URI
TYPE_CLASS_TEXT 的变体,用于输入网址和 Uniform Resource Identifier (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 预测文本或需要多个步骤来组成字形或单词,您可以在文本字段中显示进度,直到用户提交单词,然后用完成的文本替换部分合成。通过向文本添加 span,您可以在将其传递给 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);

拦截硬件按键事件

尽管输入法窗口没有显式焦点,但它首先接收硬件按键事件,并且可以消耗这些事件或将其转发给应用。例如,您可能希望在合成期间使用方向键在您的界面中导航以进行候选词选择。您可能还希望捕获返回键以关闭源自输入法窗口的任何对话框。

要拦截硬件按键,请覆盖 onKeyDown()onKeyUp()

对于您不想自己处理的按键,调用 super() 方法。

创建 IME 子类型

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

  • 一个语言区域,例如 en_US 或 fr_FR
  • 一种输入模式,例如语音、键盘或手写
  • IME 特定的其他输入样式、形式或属性,例如 10 键或 QWERTY 键盘布局

模式可以是任何文本,例如“keyboard”或“voice”。子类型也可以公开这些组合。

子类型信息用于从通知栏访问的 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>

为了确保您的子类型在界面中正确标记,使用 `%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,输入法界面定义使用它来设置子类型的标签,定义如下

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

此设置使子类型的显示名称与语言区域设置匹配。例如,在任何英语区域设置中,显示名称都是“English (United States)”。

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